-
Notifications
You must be signed in to change notification settings - Fork 416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[3, 4 단계 - 체스] 이든(최승준) 미션 제출합니다. #815
Conversation
- Result 객체 생성 - 무승부 추가 - 왕의 생존 여부로 게임 결과 판단
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 이든! 이번 단계도 잘 구현해주셨네요! 👍🏿
전체적으로 코드가 깔끔해서 코멘트가 많지 않습니다!
확인 부탁드려요!
public class DBConnectionPool { | ||
private static final int MAX_CONNECTION_SIZE = 3; | ||
private static final Deque<Connection> CONNECTION_POOL; | ||
|
||
static { | ||
CONNECTION_POOL = new ArrayDeque<>(); | ||
for (int i = 0; i < MAX_CONNECTION_SIZE; i++) { | ||
try { | ||
CONNECTION_POOL.add(DBConnectionGenerator.generate()); | ||
} catch (SQLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} | ||
|
||
public static Connection getConnection() { | ||
if (!CONNECTION_POOL.isEmpty()) { | ||
return CONNECTION_POOL.pop(); | ||
} | ||
throw new RuntimeException("현재 남아있는 커넥션이 없습니다."); | ||
} | ||
|
||
public static void releaseConnection(final Connection connection) { | ||
CONNECTION_POOL.add(connection); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
커넥션 풀은 멀티 스레드 환경을 고려해서 사용하게 될 것 같은데요! 이 클래스 스레드세이프한 클래스일까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 고려한 부분에 대해서 너무 좋은 리뷰인 것 같습니다!!
스레드 세이프하도록 변경해보겠습니다! 감사합니다:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syncronized 키워드를 적용해주셨네요! 추후 자바에서 동시성을 제어하기 위해 제공하는 기능들에 대해서도 공부해보시면 좋을 것 같아요! 👍🏿
@@ -30,3 +30,4 @@ out/ | |||
|
|||
### VS Code ### | |||
.vscode/ | |||
/docker/db/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
실행해보려면 init.sql이 필요할 것 같아서 docker/db/mysql/data
만 ignore처리 부탁드려요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗,,! 제가 설명을 달아놓지 않았네요,,!
docker-compose up
수행하신 뒤에 resources/schema.sql
디렉토리에 있는 쿼리 전부 복사하셔서 실행시켜주시면 됩니다!
import chess.domain.position.ChessRank; | ||
import chess.domain.position.Position; | ||
|
||
public class PieceEntity { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
분명 존재하는 방식이긴 하지만, 개인적으론 생산성 저하가 단점으로 다가오곤 합니다! 상황에 따라 다른 것 같네요!
DB의 엔티티와 도메인엔티티를 같은 클래스로 사용할 경우 도메인이 DB에 의존하게 구현되어야하는 단점이 분명 존재합니다.
코어한 도메인 로직을 보호하기 위해 DB접근을 위한 엔티티를 따로 만들어서 사용할 경우엔 생산성의 단점이 존재하구요.
제 경험상 DB를 걷어내거나 하는 경우는 흔치 않았던 것 같아서 저는 그냥 같은 클래스로 묶어서 사용하곤 합니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 코드의 복잡성이 증가하고 생산성이 저하되는 문제가 더 크다고 생각해서 알렉스 방식에 동의하지만
DB 설계를 먼저한 뒤에 개발을 시작한 것이 아니라, 개발 도중 DB를 설계하고 연결하려고 하니
도메인 객체의 필드와, 데이터베이스 테이블의 컬럼이 일치하지 않아 둘을 연결시키는 것이 굉장히 복잡하네요..
DB에서는 위와 같이 기물이 위치 정보를 가지고 있지만,
자바 코드에선 Rank,File 을 포함한 기물의 위치 정보를 ChessBoard 객체가 가지고 있어서
DB 테이블과 객체를 매핑하기가 굉장히 어려웠습니다..
분명 두 방식의 장단점이 존재하지만, 저는 현재 설계에는 Entity 클래스를 활용하는 방식이 적합한 것 같다고 생각이 되어 그대로 채택하겠습니다! 🙂
각 방식의 장단점을 비교해주신 좋은 답변 감사드립니다 알렉스!!
private PieceType type; | ||
private PieceColor color; | ||
private ChessRank rank; | ||
private ChessFile file; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DB접근을 위한 클래스니까 DB자료형에 맞는 원시 타입들은 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그게 맞는 것 같습니다!
변경해보겠습니다:)
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public class GameDAO implements GameRepository { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DAO 라고 작성해주신 클래스의 거의 모든 메소드가 다음과 같은 패턴인 것 같아요.
- 커넥션 맺기
- prepareStatement 세팅
- 쿼리실행 or 쿼리 실행결과 매핑 및 반환
이러한 패턴인데 이 중복되는 패턴을 어떻게하면 줄일 수 있을까요? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
작성해주신 TransactionManager라는 클래스에 힌트가 있는 것 같네요! 잘 부탁드립니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
패턴은 비슷하지만 중복되는 코드는 생각보다 적은 DAO
로직을
TransactionManager
와 비슷한 방식으로 처리하기에는 제 지식이 많이 부족했던 것 같습니다 🥲
대신 Fluent API
방식을 사용해서 중복되는 코드를 메서드 체이닝 형태로 풀어내보았습니다.
혹시 이 방식은 어떠신지, 그리고 개선할 점은 뭐가 있을 지 남겨주시면 잘 반영해보겠습니다!
infra/db/query
패키지 내부에 QueryManager
라는 클래스입니다!
Before
@Override
public Long add(Connection conn, GameEntity game) throws SQLException {
try {
PreparedStatement preparedStatement = conn.prepareStatement(
"INSERT INTO game (turn) VALUES(?)",
Statement.RETURN_GENERATED_KEYS
);
preparedStatement.setString(1, game.getTurn().now().name());
preparedStatement.executeUpdate();
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();
return generatedKeys.getLong(1);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
After
public Long add(Connection conn, GameEntity game) throws SQLException {
ResultSet generatedKeys = QueryManager
.setConnection(conn)
.insert("INSERT INTO game (turn) VALUES(?)")
.setString(1, game.getTurn().now().name())
.execute()
.getGeneratedKeys();
generatedKeys.next();
return generatedKeys.getLong(1);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 단계에서 할 수 있는 충분한 고민을 해주신 것 같아요!
추후 미션에서 스프링을 통해 DB를 접근하는 코드들을 짜게 될텐데 그때 스프링에서 제공하는 JdbcTemplate
이라는 클래스의 코드를 참고해보시면 도움이 될듯 합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 이든! 레벨1 고생 많으셨습니다! 이번 단계에서 고민할만한 부분들을 충분히 고민해주신 것 같아 PR은 이만 머지하겠습니다! 추가적으로 추후 레벨에서 고려해봄직한 것들을 코멘트로 남겨두었는데요! 여유가 되신다면 한번 살펴보아도 좋을 것 같습니다! (굳이 살펴보지 않아도 이후 레벨에서 결국 만나게 되긴 할 것 같네요..😅) 다음 레벨도 화이팅이에요! 💪
질문 답변
결국 game 커맨드에 대한 처리는 기존 코드처럼 분기처리를 할 수 밖에 없었는데요,,
제가 느끼기에는 이 부분이 처리가 어려운 건 설계 미스인 것 같다고 생각합니다..
때문에 어떻게 개선하면 좋을 지, 알렉스의 의견을 얻어보고자 질문을 드리게 되었습니다!
현재 작성해주신 코드 구조상 단순한 분기처리 코드와 딱히 차별점이 느껴지진 않습니다! 근데 결국 이러한 코드를 고민해주시는 이유는 코드의 가독성과 유지보수성이 이유일 것 같은데, 현재 요구사항과 시스템 구조에서 꼭 분기문을 없애야하는 건지 의문이 드네요..! 단순한 분기문 나열이 가독성이 더 좋고 유지보수하기 좋은 경우도 있다고 생각하거든요!
안녕하세요 알렉스 이든입니다! 🙌
체스 미션에 DB를 적용해서 구현하려니 너무 어지러웠습니다...! 🥲
그래서 구현에 조금 많은 시간이 투자되었네요..!
이번 리뷰도 잘 부탁드립니다!!
주요 구현 사항
DB 적용
애플리케이션 실행 시에 미리 커넥션을 만들어 사용할 수 있는 커넥션 풀을 공부차원에서 구현해보았습니다.
TransactionManager
클래스 구현하였습니다.재입력 로직 구현
유저 입장에서 좋지 않을 것이라고 생각되어 재입력 로직을 구현하였습니다.
고민사항
구현하면서 아래와 같은 고민사항이 있었습니다.
도메인 객체와 DB 테이블의 데이터 구조가 일치하지 않는 문제
이전에 만들었던 도메인 객체와 DB의 테이블을 매핑하여 간단한 CRUD 로직을 만들고 싶었으나,
그렇게 하려면 코드에 수정해야할 사항이 너무 많이 생겨
Entity
클래스를 만들어 해결하였습니다.엔티티 클래스는 DB 테이블과 완전히 동일한 데이터를 가지도록 하였으며, CRUD 작업에 사용됩니다.
게임의 커맨드들은 어떻게 관리해야할까?
이 부분은
Command 패턴
을 적용해서 해결하였습니다.Controller에서 굉장히 복잡한 분기처리를 수행해야했던 이전 커맨드 관련 코드에
커맨드 패턴을 적용하여 각각의 커맨드 구현체에게 책임을 분산시켰습니다.
이를 통해 분기처리를 제거할 수 있었습니다.