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
[Spring 체스 - 2단계] 써머(최혜원) 미션 제출합니다. #474
Conversation
int boardId = boardRepository.getBoardIdByRoom(roomId); | ||
Board board = loadBoard(boardId); | ||
board.loadTurn(boardRepository.getTurn(boardId)); | ||
int boardId = gameRepository.findBoardIdByRoom(roomId); |
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.
이런 경우에는 BoardDao
만 이용하면 되는데, 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.
boardId를 단독으로 가져와서 다시 board를 조회할 필요가 있을까요?
아래와 같은 방식으로 할 수 있지 않을까요?
board = repository.findByRoomId(roomId);
@GetMapping | ||
public ResponseEntity<List<Map<String, String>>> findRooms() { | ||
return ResponseEntity.ok(roomService.findRooms()); | ||
} |
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.
이 부분은 프론트에 방 목록을 보내주기 위해 필요한 부분인데요,
기존에 Room
, RoomDto
가 이미 있으나, 여기에는 모두 비밀번호를 나타나는 password
필드가 있습니다
그런데 api에 비밀번호를 담아 주고 싶지 않고, id
와 name
만 담는 dto 클래스를 만드려 하니,
그러면 반환 포맷에 변화가 생길 때 마다 dto를 추가해줘야 하나 싶어서 일단 Map 으로 만들었습니다...
dto를 추가해주는 게 맞을까요?
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.
dto도 각 레이어별 (controller, service, repository 등) 존재할 수 있고, 원하는 형태의 응답을 위해 여러 많은 dto가 추가되는 건 자연스러운 일이에요.
Map
을 사용하면 단순히 메서드 시그니처만 봤을 때는 어떤 응답을 만들어 주는지 알기 어렵고, 모든 값을 넣을 수 있다보니 실수로 넣지 않아야 하는 값을 넣는 문제도 있을 수 있을 것 같아요.
당장 Map
을 사용하면 dto를 새로 만들지 않아도 되니까 편하겠지만 앞으로 이런 부분들이 계속해서 생겨나면 유지보수하는 작업이 어려워지지 않을까요?
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.
써머 🏖️ , 안녕하세요!
2단계 잘 구현해주셨네요 👍
몇가지 코멘트 남겨두었으니 확인 부탁드릴게요!
궁금한 점 있으시면 편하게 DM 주세요~
gameRepository.deleteOldBoard(roomId); | ||
return gameRepository.saveGame(roomId, board); |
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.
save할 때 항상 이전 데이터의 삭제가 필요하면 saveGame 메서드에서 함께 처리하는 건 어떨까요?
PieceDto pieceDto = gameRepository.findPiece(boardId, source).get(); | ||
deleteMovedPieceFromSource(boardId, source); | ||
updateMovedPieceToTarget(boardId, target, pieceDto); | ||
updateGameState(board, boardId); |
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.
board, piece 테이블에 개별로 접근해야 하는 메서드들은 책임이 분리되지 않고, 그냥 클래스 계층만 한 단계 깊어지게 되더라고요
서비스에서 개별 테이블에 접근이 필요한 걸까요? #383 (comment)
move(...) {
board = repository.find(...);
board.move(...);
repository.save(...);
}
pieces.put(Position.of(pieceDto.getPosition()), PieceFactory.build(pieceDto)); | ||
} | ||
Board board = new Board(() -> pieces); | ||
board.loadTurn(boardDao.getTurn(boardId)); |
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.
loadTurn대신 생성자에서 turn을 주입받으면 좋을 것 같아요.
int boardId = boardRepository.getBoardIdByRoom(roomId); | ||
Board board = loadBoard(boardId); | ||
board.loadTurn(boardRepository.getTurn(boardId)); | ||
int boardId = gameRepository.findBoardIdByRoom(roomId); |
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.
boardId를 단독으로 가져와서 다시 board를 조회할 필요가 있을까요?
아래와 같은 방식으로 할 수 있지 않을까요?
board = repository.findByRoomId(roomId);
@GetMapping | ||
public ResponseEntity<List<Map<String, String>>> findRooms() { | ||
return ResponseEntity.ok(roomService.findRooms()); | ||
} |
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.
dto도 각 레이어별 (controller, service, repository 등) 존재할 수 있고, 원하는 형태의 응답을 위해 여러 많은 dto가 추가되는 건 자연스러운 일이에요.
Map
을 사용하면 단순히 메서드 시그니처만 봤을 때는 어떤 응답을 만들어 주는지 알기 어렵고, 모든 값을 넣을 수 있다보니 실수로 넣지 않아야 하는 값을 넣는 문제도 있을 수 있을 것 같아요.
당장 Map
을 사용하면 dto를 새로 만들지 않아도 되니까 편하겠지만 앞으로 이런 부분들이 계속해서 생겨나면 유지보수하는 작업이 어려워지지 않을까요?
@GetMapping(value = "/{roomId}", params = "command=load") | ||
public ResponseEntity<BoardDto> loadGame(@PathVariable int roomId) { | ||
return ResponseEntity.ok(gameService.loadGame(roomId)); | ||
@GetMapping(value = "/{roomId}/new") |
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.
POST가 적절해 보이네요!
@DeleteMapping("/{roomId}") | ||
public ResponseEntity<Map<String, String>> deleteRoom(@PathVariable int roomId, @RequestParam String password) { | ||
roomService.delete(roomId, password); | ||
return ResponseEntity.ok(Map.of("url", "/")); |
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.
이 부분은 url을 내려주지 않아도 프론트가 어디로 이동해야 할지 알 수 있지 않을까요?
Optional<Integer> boardId = boardDao.findBoardIdByRoom(roomId); | ||
Optional<RoomDto> room = roomDao.findById(roomId); | ||
checkPassword(room, password); | ||
if (boardId.isEmpty()) { | ||
roomDao.delete(roomId); | ||
return; | ||
} | ||
return roomRepository.find(name).get(); | ||
checkGameEnd(boardId); | ||
roomDao.delete(roomId); |
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.
RoomService는 개별 RoomDao와 BoardDao를 사용하도록 뒀는데, 여기에 대해 추가로 참고할 만한 키워드가 있을까요?
저수준의 CRUD는 DAO, 컬렉션 처리는 Repository 까지는 이해했는데, 제 코드에 적용하려니 어디까지가 컬렉션 처리인가? 저수준의 CRUD만 필요한 경우 dao를 바로 사용해도 되는가? 같은 고민 때문에 쉽지 않네요
Repository는 객체를 다루고, Dao는 테이블을 다룬다고 보면, 객체 구조가 너무나 단순하여 Repository가 단순 by-pass로 보여도 객체를 저장, 수정, 조회, 삭제를 하는 건 Repository를 만들어 사용하는 게 역할에 맞는 책임이라고 생각해요.
체스 코드에서는 Piece는 특정 Board에 속하고 모든 비즈니스는 Board를 중심으로 일어나기 때문에 객체 컬렉션을 처리하는 데 PieceRepository를 통하는 게 아닌 BoardRepository(GameRepository)를 통해야 합니다. 하나의 유기체처럼 동작하는 객체마다 일반적으로 Repository 한개가 만들어진다고 보면 됩니다.
Optional<RoomDto> roomDto = roomRepository.find(name); | ||
if (roomDto.isEmpty()) { | ||
roomRepository.save(name); | ||
public void delete(int roomId, String password) { |
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.
dto와 getter로 비즈니스 로직을 수행하고 있는 것 같아요.
아래와 비슷한 구조로 변경해보면 어떨까요?
void delete(...) {
room = repository.findById(roomId);
if (!room.isCorrect(password) && ...) {
throw ...
}
repository.delete(room);
}
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public class 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.
👍
안녕하세요, 핀! 남겨주신 피드백 모두 반영했습니다 🙇 |
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.
써머 🏖️ , 안녕하세요!
리뷰 반영 잘 해주셨네요~ 👍
이번 미션은 여기서 마무리하도록 할게요!
궁금한 점 있으시면 언제든지 DM 주셔도 좋습니다~
고생하셨습니다~~
public class Room { | ||
|
||
public static final int NAME_LENGTH_LIMIT = 16; | ||
public static final int PASSWORD_LENGTH_LIMIT = 16; | ||
|
||
private long id; | ||
private final String name; | ||
private final String password; |
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.
도메인과 엔티티를 분리하는 것도 좋고, id 생성을 DB에 의존하지 않고 코드 레벨에서 uuid를 생성하는 방법도 있을 것 같아요 ㅎㅎ
역시나 정답은 없다고 생각해요 :)
안녕하세요, 핀!
저번에 남겨주신 리뷰 감사히 잘 읽었습니다 🙇
DAO와 Repository의 차이에 대해 남겨주신 코멘트에 질문이 있는데요,
2단계로 넘어오면서 GameRepository가 PieceDao와 BoardDao를 갖게 했습니다
그러니 GameService에서 piece, board 테이블에 각각 저장을 실행하지 않고,
gameRepository.saveGame(roomId, board);
하는 식으로컬렉션 처리
책임을 GameRepository로 분리할 수 있었습니다그런데 이렇게 하니 board, piece 테이블에 개별로 접근해야 하는 메서드들은 책임이 분리되지 않고, 그냥 클래스 계층만 한 단계 깊어지게 되더라고요
그래서 GameService에서 각 dao 까지 갖게 하려니 계층 구조에 일관성이 없어서 좋지 않다고 생각했습니다
일단 GameService는 GameRepository를 사용하도록, RoomService는 개별 RoomDao와 BoardDao를 사용하도록 뒀는데, 여기에 대해 추가로 참고할 만한 키워드가 있을까요?
저수준의 CRUD는 DAO, 컬렉션 처리는 Repository 까지는 이해했는데, 제 코드에 적용하려니
어디까지가 컬렉션 처리인가?
저수준의 CRUD만 필요한 경우 dao를 바로 사용해도 되는가?
같은 고민 때문에 쉽지 않네요그리고 컨트롤러 테스트의 경우 추가하고 있었는데, mock mvc를 사용하지 않고 하려니 통합 테스트가 되고, 메서드 순서가 보장되지 않아 데이터 삽입 시점을 알 수 없고, 데이터를 삭제해도 auto_increment 설정 된 키값이 초기화 되지 않는 등의 문제 때문에 의도대로 잘 되지 않았습니다 😭...
그래서 mock mvc를 공부 중인데 제출이 너무 늦어지는 것 같아 우선 pr을 올립니다, 일단은 주석처리 해뒀어요