[4, 5단계 - 체스] 조엘(조영상) 미션 제출합니다.#236
Conversation
|
안녕하세요 미립! 조엘입니다!
1. DB 구조를 바꿨어요 특히 캐슬링, 프로모션의 특별행마들은 한 수에 두 가지 기물 위치가 바뀌거나, 기물의 종류가 바뀌게 되어요. 움직인 기물을 찾고, 어디로 움직였는지를 찾아, 이를 기존 DB에 적용하려면, 도메인의 수정이 불가피해요. (어쩌면 제가 도메인을 잘못 짠 걸 수도 있겠다 생각했어요.) 하지만 바뀐 DB 구조는 update 구현이 훨씬 수월했어요. 그저 이동 후 현재 체스게임의 화이트팀/블랙팀들의 Map<Position, Piece> 를 string으로 바꿔주어 저장만 하면 되어요. 또한 DB 연결의 시간도 줄 것이에요. 기존에 BlackTeam에 알맞는 체스 기물에 대한 정보를 DB에서 가져오려면, team='black'에 해당하는 ResultSet을 모두 돌면서 하나하나 기물을 생성해 줘야 했어요. 하지만 바뀐 DB는 한 번에 접근으로 black팀의 기물 정보들을 string으로 가져오고 이를 프로그램 안에서 parsing하여 기물을 생성하니, 성능 향상으로 이어지리라 생각이 들었어요. 2. Controller, Service, DAO를 Stateless하게 만들었어요 Stateless한 객체는 multi-thread환경에서 thread-safe하다는 글을 읽었어요. 하지만, 인스턴스 변수가 없으면 thread-safe하겠다는 것에는 수긍이 되어서 리팩토링 진행했습니다! 3. HTTP 상태코드를 Enum으로 관리해요 4. JDBC API의 자원을 해제해요 현재 제 코드는 하나하나 connection, statement를 닫아 주도록 작성했어요.
1. 한 메서드에 동시에 요청이 오면? 읽어주셔서 감사합니다! 코로나 조심하세요 미립! 😥😥💪💪 |
seok-2-o
left a comment
There was a problem hiding this comment.
피드백 반영 잘해주셨네요 :)
질문에 답변 및 추가적인 피드백 몇가지 남겨 놓았으니 확인 부탁드려요 :)
추가로 지금 데이터베이스에 기물정보를 한 컬럼에 모두 넣어놓고 파싱해서 사용하고 있어요.
RDB 는 말그대로 관계형(Relational)으로 구성되는게 좋다고 생각해요.
지금 단계에서 수정을 할 필요는 없다고 생각하지만 조엘도 한 번 가볍게 고민해보면 좋을 것 같아요.
| return gson.toJson(DBConnectionDTO.fail()); | ||
| } catch (IllegalArgumentException e) { | ||
| res.status(BAD_REQUEST.statusCode()); | ||
| return gson.toJson(DBConnectionDTO.fail()); |
There was a problem hiding this comment.
BAD_REQUEST.statusCode() 임에도 불구하고 DBConnectionDTO.fail()을 응답하고 있네요 🤔
There was a problem hiding this comment.
실수입니다... 우선 동작을 테스트하기 위해 임시로 적어놨던 것을 깜빡하고 수정하지 않았네요. 😭
다음과 같이 move에 대한 bad_request는 MoveResponseDTO로 응답을 처리하도록 변경했습니다!
376b610
| final String server = "localhost:13306"; // MySQL 서버 주소 | ||
| final String database = "chess_db"; // MySQL DATABASE 이름 | ||
| final String option = "?useSSL=false&serverTimezone=UTC"; | ||
| final String userName = "root"; // MySQL 서버 아이디 | ||
| final String password = "root"; // MySQL 서버 비밀번호 |
There was a problem hiding this comment.
상수로 관리해도 괜찮을 것 같은데 메소드 변수로 관리하는 이유가 있으신가요? 🤔
There was a problem hiding this comment.
상수로 관리하는 것이 가독성이 좋고, 메모리 상 접근 측면에서도 빠르다고 판단하여 상수로 변경했습니다!
c6ec39c
| } | ||
|
|
||
| public synchronized ChessGameDTO startNewGame() throws SQLException { | ||
| chessGameDAO.deleteChessGame(); |
There was a problem hiding this comment.
게임이 진행중에 있는데, 다른 사람이 새로운 게임을 시작하면 기존게임이 삭제될 것 같아요 😱
There was a problem hiding this comment.
제가 이전에 말씀드린 동시에 여러 게임은
게임 1 은 저와 조엘이 게임을 진행하고
게임 2는 포비와 제이슨이 진행하는 것이 동시에 불가능할까? 라는 질문이였습니다.
해당부분에 대해서도 고민해주세요 :)
| this.chessGameDAO = new ChessGameDAO(); | ||
| } | ||
|
|
||
| public synchronized ChessGameDTO startNewGame() throws SQLException { |
There was a problem hiding this comment.
인스턴스 변수가 없는데 구지 synchronized 하게 만들어 줄 필요는 없을 것 같아요.
메소드가 실행되면 메소드에서 사용되는 변수는 독립적인 메모리영역에 로드되고 삭제될 것이에요
따라서 여러 메소드가 동시에 실행되어도 간섭을 받지 않아요.
There was a problem hiding this comment.
오히려 synchronized 키워드 때문에 성능이 저하 될 수 있을 것 같아요.
There was a problem hiding this comment.
멀티 스레드 환경을 가정하고 생각을 했을때,
특정 스레드가 특정 메서드를 실행한다면,
각 스레드는 고유하게 스택을 가지고 있기 때문에,
각 스레드 고유 스택에 메서드에 필요한 변수/함수들이 적재되고 실행되고 없어질 것이에요.
현재 ChessService는 stateless하게 구현되어 있기 때문에,
다양한 스레드에서 작업을 실행해도 바뀔 인스턴스 변수가 없어요.
따라서 굳이 synchronized 예약어를 사용해 data의 thread-safe를 보장할 필요가 없어요.
다음과 같이 리팩토링 진행했습니다! 감사합니다 미립 :)
646ab21
|
|
||
| public synchronized ChessGameDTO startNewGame() throws SQLException { | ||
| chessGameDAO.deleteChessGame(); | ||
| final ChessGame chessGame = new ChessGame(Team.blackTeam(), Team.whiteTeam()); |
There was a problem hiding this comment.
createChessGame이라는 메서드 내부에서 ChessGame을 생성하고 DB에 저장하는게 훨씬 메서드 의미에 맞고 자연스럽네요!
다음과 같이 수정했습니다 감사합니다 :)
7036c22
|
|
||
| public synchronized ChessGameDTO move(final String start, final String destination) throws SQLException { | ||
| final ChessGame chessGame = chessGameDAO.readChessGame(); | ||
| System.out.println("chessGame = " + chessGame); |
There was a problem hiding this comment.
소소하지만 System.out.println 로 찍는 출력들이 쌓이면 성능에 안좋은 영향을 미칠 수 있습니다.
| final double whiteTeamScore = chessGame.calculateWhiteTeamScore(); | ||
| final double blackTeamScore = chessGame.calculateBlackTeamScore(); | ||
| final boolean isPlaying = chessGame.isPlaying(); | ||
| return new ChessGameDTO(piecePositionToString, currentTurnTeam, whiteTeamScore, blackTeamScore, isPlaying); |
There was a problem hiding this comment.
이전 규칙에 메소드가 가지는 파라미터는 4개 이하로 유지하는 조건이 있는데 이를 지키도록 리펙토링 해보는건 어떤가요?
| private final String DTOFormat; | ||
| private final String DAOFormat; |
There was a problem hiding this comment.
사실 동일한 정보가 아니라고 생각했어요.
DTOFormat은 서버에서 팀에 대한 정보를 프론트단으로 넘겨줄 때, 팀을 어떠한 형식으로 넘겨줄지를 지정하고,
DAOFormat은 서버에서 팀에 대한 정보를 DB에 저장할 때, 팀을 어떠한 형식으로 저장할 지를 지정해요.
예를 들어 blackTeam을 프론트단에는 "black"으로 넘겨줘야하고, DB에는 "B"와 같은 형식으로 저장해야 한다면, 해당 클래스가 유용하리라 생각해요.
아직 DAO, DTO를 많이 접해보지 못해 확신은 없지만,
혹시나 매번 그 둘이 같은 정보를 같은 형식으로 표현하도록 코드를 작성해야 정석적인 것이라면, 해당 클래스는 굳이 필요가 없을 수도 있을 것 같아요.
|
안녕하세요 미립! 조엘입니다!!
1. ChessService의 메서드들의 synchronized 예약어를 삭제했어요. 제가 처음 synchronized를 도입했던 이유는, ChessService에서 domain 객체를 인스턴스 변수로 가졌기 때문이에요. 멀티 쓰레드 환경에서 해당 domain 객체가 의도대로 동작하지 않는 것을 방지하고자 synchronized 예약어를 붙였어요. 하지만 리팩토링을 통해 ChessService가 domain 객체를 갖지 않도록 stateless하게 변환을 했어요. 따라서 다양한 스레드에서 ChessService 내의 메서드를 실행해도, race-condition이 발생할 인스턴스 변수가 없어요. 덕분에 stateless하게 구현하는 것의 장점을 느껴볼 수 있었습니다 :) 따라서 synchronized 예약어를 삭제했어요! 2. 진행 중인 게임이 있는 경우, 새로운 게임 요청을 받는다면? 해당 방식을 다음과 같이 개선했어요. 사용자가 새로운 게임을 시작하겠다라고 답변하면, forceNewGame 이라는 API로 요청을 보내 기존 게임 정보를 삭제하고 새로운 게임을 만들도록 했습니다. 하지만 해당 방식은 치명적인 약점이 있어요. 그냥 아무 사용자나 url에 /forceNewGame을 입력하면 기존 데이터는 사라지고 새로운 게임을 시작시킬 수 있을 것이에요. 프로를 위한 웹 기술 입문이라는 책을 읽고 있는데, 해당 문제는 유저를 생성하여 인가된 사용자에 한에 세션을 발급하면 해결할 수 있으리라 생각됩니다! 3. 미립이 남겨주신 피드백들을 반영했어요!
|
|
Qs. Ans. 1. 데이터에 대한 쿼리가 불가능해요 2. DB 마이그레이션이 어렵다 |
|
사실 Web/DB 설계에 더 집중하는 것이 맞는 것 같지만, 1단계 필독서인 프로가 되기 위한 웹 기술 입문 이라는 책을 읽으면서 제 체스게임을 배포해보고 싶어졌어요. gradle 빌드 도구의 편안함을 느낄 수 있었고, 제 로컬에서만 사용했던 docker를 직접 서버 컴퓨터에서 실행해 보는 것도 좋은 공부라고 느꼈어요. 책에서는 Apache와 tomcat을 예시로 설명하는데, 저는 스파크자바에서 기본으로 제공하는 jetty 웹서버를 사용했어요. 웹 서버(Apache)와 웹 애플리케이션 서버(tomcat)에 대해 책에서 언급하는데 아직 웹 애플리케이션 서버에 대해 감이 잘 안잡혀요. 빌드 후, 서버에서 nohup sudo ./gradlew run 명령을 실행했어요. http://ec2-3-36-97-77.ap-northeast-2.compute.amazonaws.com/ 여기에서 체스게임을 해보실 수 있어요! |
학습로그[Architecture] Service Layer내용
링크[JS] fetch API내용
링크[JDBC] 자원 반환내용
링크
[Java] 멀티 스레드 환경에서 고려할 것내용
링크 |
seok-2-o
left a comment
There was a problem hiding this comment.
벌써 배포까지 진행하시다니 👍 👍
덕분에 체스도 한게임 진행해보았습니다 ㅎㅎㅎ
워낙 잘 해주신 덕분에 질문한가지 남겨놓고 이만 종료하도록 하겠습니다.
다음 미션도 잘 해내시라 믿고 응원하겠습니다.
저와의 미션은 종료되었지만, 앞으로도 궁금한점이나 어려운점 있다면 편하게 DM 주세요.
감사합니다. 🙇

안녕하세요 미립! 우테코 3기 조엘입니다.
체스 미션 웹UI + DB 미션 진행해봤어요.
1. WEB UI 작업을 진행했어요
사실 HTML,CSS,JS를 해본적이 있어서 UI 만드는 것은 재미있었어요.
하지만 fetch API를 통한 JS 비동기 통신은 처음 적용해봐서 애를 먹었어요 😅
JS 투두리스트 미션 1,2 단계를 거치면서 개념을 잡았고, 이를 코드에 적용해봤어요.
최대한 Single Page Application을 만들기 위해 노력했고, 결과적으로 애정이 담긴 체스판이 탄생했어요 :)
2. SparkJava + Gson 을 적용했어요
자바 웹 프레임워크 중 하나인 SparkJava를 공부하고 적용해봤어요.
각 요청 URL에 따른 요청 처리 로직들을 구현했어요.
응답은 Gson 라이브러리를 사용해, DTO를 JSON으로 변환해 View단으로 넘겨줘요.
처음 써보는 프레임워크/라이브러리라서 내부 동작을 완벽히 이해하진 못하고 우선 돌아만 가도록 코드를 작성한 것 같아요...!
3. DTO와 DAO를 만들었어요
사실 이전 미션까지 DTO를 사용해본 적이 없었어요.
도메인 객체의 정보 중 다른 계층에 필요한 정보만 뽑아, 이를 객체화 시켜 넘겨주는 역할을 하는 클래스라고 개념만 알고 있었어요.
제대로 제가 DTO를 만들었는지 궁금합니다. 🤔
DAO라는 것도 처음 사용해봤어요.
DB에 저장하기 위해 DAO라는 것을 사용한다고 데이터베이스 기초 수업 시간에 배웠어요. 수업에 나온 코드를 기반으로, DB와의 커넥션을 적용해봤어요.
DAO 역시 똑바로 만들었는지 궁금합니다. 🤔
4. MySQL을 통해 DB를 적용했어요
사실 데이터베이스라는 것을 깊게 배워본 적이 없었어요.
쿼리도 처음 날려봤고, MySQL 세팅하는 것도 처음이었어요.
데이터베이스 기초 수업을 듣고, 필독서 SQL 첫걸음을 읽으면서 적용했던 것 같아요.
체스 게임에 사용한 테이블 구조는 README에 적어놨어요.
하나 고민했던 부분이 있어요.
DB에 접근하는 DAO CRUD 로직 중 update를 구현하지 않았어요.
move 명령으로 받은 기물의 예전 위치/현재 위치를 통해서 Update를 구현할 수 있겠다고 생각했어요. 현재 piece_position 테이블은 position을 Primary key로 들고 있거든요.
예전 위치를 통해 알맞은 piece_position row를 가져오고, 현재 위치로 해당 row를 업데이트 할 수 있겠다 생각했어요.
하지만 이러한 방식은 한 move에 두 가지 기물의 위치를 바꾸는 캐슬링이나,
move를 하면서 기물의 종류를 바꾸는 승격에 문제가 발생하리라 생각했어요.
따라서 현재, 저장 버튼을 누르면, 테이블의 정보를 모두 삭제하고 현재 진행중인 체스게임의 정보를 저장하는 방식을 채택했어요.
가장 큰 이유는 특별행마에 대한 DB 업데이트를 위해, 도메인의 객체들의 로직을 수정하고 싶진 않았어요.
또한 32개의 기물에 대한 정보만 저장하면 되니 DB 저장에 크게 병목이 발생하지 않을 것이라 생각했어요.
미립의 생각이 궁금합니다!