[BE] TEAM-11 Jane & Jung PR드립니다.#34
[BE] TEAM-11 Jane & Jung PR드립니다.#34janeljs wants to merge 61 commits intocodesquad-members-2021:TEAM-11from
Conversation
- Team와 Player이 History와 각각 1:N관계를 맺도록 클래스에 List<History> 추가
DB 설계 및 mock API 구성
[BE] Controller 메서드 구현
- WebConfig에서 전역에 cross origin을 적용하는 방법에서 컨트롤러별로 적용하는 방식으로 변경
There was a problem hiding this comment.
우선 프로젝트를 일정에 맞게 마무리하고 기능 구현을 완료한 것은 칭찬합니다. 👍 💯 수고 많으셨습니다.
그러나 세부적인 구현 면에서 보면 다소 실망스러운 감이 있습니다.
특히 GameService 부분의 경우 제대로 들여다보지 못했는데요, 아무리 읽어도 코드가 잘 이해되지 않는 부분이 많네요.
GameService 뿐만이 아니라, 프로젝트 전반의 코드들을 다시 들여다보며, 아래 원칙에 입각하여 더 좋은 설계를 고민해 주시면 좋겠습니다.
원칙
- 분리한다. 객체가 비대해지지 않도록 관리한다.
- 3개 이상의
private메소드가 만들어진다면 우린 무언가 잘못 하고 있는 것이다.
- 3개 이상의
- 도메인 객체에게 더 많은 역할 을 부여한다.
- 감싼다,
int,String과 같은 기본 클래스에 의존하는 로직을 걷어낸다.- 유효성 검증이 필요한 모든 값은 감싸는 것이 원칙이다.
Component,Service선언하는 것에 부담감을 가지지 않는다. 책임 분리를 위해 필요하다면 과감히 그렇게 한다.- 꺼내지 말고 가공한다. 가공하지 말고 물어본다.
수고 많으셨습니다. 👍
| @Getter | ||
| @AllArgsConstructor | ||
| @JsonPropertyOrder({"matchId", "inning", "nextHitter", "expeditionTeam", "homeTeam", "pitcher", "hitter", "teamLog"}) | ||
| public class ApiResponse { |
There was a problem hiding this comment.
헐, 하나의 클래스에 이렇게 모든 정보가 담겨있어야 할까요?
클라이언트 개발자들과 협의한 결과겠죠? 그렇다면 어쩔 수 없겠지만요.
There was a problem hiding this comment.
선수가 교체될때마다 업데이트된 정보를 반영해 (선수 히스토리, 공수교대, 득점 등) 백엔드에서 화면에 필요한 모든 정보를 response해주기로 협의했습니다. 필요한 모든 정보를 하나로 묶기 위한 DTO클래스라고 생각해주시면 좋을 것 같습니다 ㅜㅜ
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor |
There was a problem hiding this comment.
조금 위험한 생성자인 것은 알고 있을 것 같네요.
이걸 개선하려면 어떻게 해야할지도 같이 고민해주셨으면 좋았겠는데요. 아쉽네요.
There was a problem hiding this comment.
여기 에 따르면 같은 타입의 필드 변수의 순서가 바뀔 경우, @AllArgsConstructor로 생성자 매개변수의 순서도 자동으로 바뀌어 문제가 발생할 수 있다고 합니다.
애노테이션 사용없이 직접 생성자를 사용하거나, 빌더를 쓰면 좋을 것 같은데 생각하지 못했던 것 같습니다. 지적 감사합니다!
| this.pitchCount = player.getPlayerGameInfo().getPitchCount(); | ||
| this.plateAppearances = player.getPlayerGameInfo().getPlateAppearance(); | ||
| this.hits = player.getPlayerGameInfo().getHits(); |
There was a problem hiding this comment.
어려운 말로 디미터 법칙 위반 이라고도 하던데요.
이걸 개선하려면 어디서 어떤 수정이 이뤄져야할까요?
There was a problem hiding this comment.
생성자에 PlayerGameInfo를 직접 넣어주면 게터를 연달아 사용하지 않을 수 있을 것 같습니다.
다만 이것은 임시방편인 것 같습니다. 사실 Player과 PlayerGameInfo가 설계상 엮여있으면 안되는 객체라고 (뒤늦게) 생각했습니다. Player에는 Match에 영향받지 않는, 무관한 정보만 담기도록하고, 선수의 타석, 안타 수 등 경기에 따라 다른 값들은 Match에 담겨있는 것이 맞는 것 같습니다.
만약 시간을 돌이킬수있다면, PlayerGameInfo를 Match를 aggregate root로 하도록 엮고, Player은 Team와 함께 Match와 별도의 aggregate가 되도록 수정하고 싶네요ㅜㅜ 위의 PlayerDTO 생성자에는 Player과 PlayerGameInfo를 받도록 하고 싶습니다.
| import java.util.List; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor |
There was a problem hiding this comment.
잘못 넣은 것 같습니다;;ㅎㅎ JPA때랑 헷갈렸는지, 왜 추가해줬는지 모르겠네요. 심지어 Entity도 아니고 DTO인데! 바로 삭제하겠습니다
| for(Player player: team.getPlayerList()) { | ||
| playerGameScore.add(new PlayerGameScoreDTO(player)); | ||
| } |
There was a problem hiding this comment.
List 와 같은 컬렉션 성격의 변수를 다루는 경우 원소를 따로 빼서 .add() 연산을 하는 것보다는, 리스트 자체를 통째로 갈아끼우는 구현이 사이드 이펙트를 방지하는 방향입니다.
Player 를 Team 에서 하나씩 꺼내서... DTO 로 변환한 후에 list 에 하나씩 밀어넣는 것보다, Team 이 PlayerGameScoreDTO 리스트를 통째로 딱 내려주는게 더 좋을 텐데요.
There was a problem hiding this comment.
리스트 자체를 통째로 갈아끼우는 구현 -> 이렇게 하고 싶었는데 더 좋은 방법을 고민해보겠습니다.
Team 이 PlayerGameScoreDTO 리스트를 통째로 딱 내려주는게 더 좋을 텐데요. -> Team에서 DTO를 몰랐으면 하는데, 어떻게 구현할지 감이 잡히질 않네요. 한번 말씀대로 수정 시도해보겠습니다!
| public ResponseEntity<PlayerListPopUpDTO[]> showPlayerList(@PathVariable Long matchId) { | ||
| return new ResponseEntity<>(gameService.getPlayerInfo(matchId), HttpStatus.OK); | ||
| } | ||
|
|
||
| @GetMapping("/{matchId}/record/teams") | ||
| public ResponseEntity<TeamGameScoreDTO[]> showDetailScoreList(@PathVariable Long matchId) { |
There was a problem hiding this comment.
Generic 안에 List를 넣어도 됩니다. 배열을 써야 할 특별한 이유가 있는게 아니라면, 그렇게 개선해보시는게 좋겠어요.
public ResponseEntity<List<TeamScoreDto>>There was a problem hiding this comment.
배열로 하니까 add 과정이 없어 코드 두 줄을 절약할 수 있어 저렇게 했는데, list가 컨벤션인가 보네요! 지금은 배열에 원정팀 스코어, 홈팀 스코어 두개뿐이므로 문제가 없어 보일 수 있는데, 나중에 배열에 다른 값들이 추가된다면 리스트가 더 유연하게 대응할 수 있을 것 같습니다. 몰랐는데 감사합니다!
|
|
||
|
|
||
|
|
||
| public class Constants { |
There was a problem hiding this comment.
상수를 분리해서 선언해둔 것은 좋습니다만...
아래 필드들을 보니, 별도의 enum으로 분리하여 관리해야 할 항목들이 좀 보이네요.
예를 들어 STRIKE, BALL... 의 경우, 플레이의 결과물이므로 분리되는 게 좋았을 것 같고,
PITCHER, HITTER 와 같은 것도 선수의 포지션에 관한 enum일 거고요.
설계상에 고려가 필요하지 않았나 생각됩니다.
There was a problem hiding this comment.
검색했더니 JDBC에서 enum을 지원하지 않는다고 해서 DB에 저장할 방법이 없는 줄 알았습니다! 잘못 알았네요. 처음에 enum을 만들었다가 에러가 떠서 jdbc연동문제인줄 알고 다시 지웠었는데, 더 공부해보고 다른 분들 코드도 많이 참고할 걸 그랬습니다. enum으로 유연한 설계 해보겠습니다! 감사합니다.
| @Id | ||
| @JsonIgnore | ||
| private Long id; | ||
| private String actionName; |
|
|
||
| private int out; | ||
| private int inningNumber; | ||
| private String role; |
There was a problem hiding this comment.
사실 야구는 본디 홈 팀이 말 공격이기때문에, 초/말 구분만 보고도 공수를 알 수 있기는 합니다.
There was a problem hiding this comment.
Inning이 사실 response(ApiResponse클래스)에 그대로 담기는 , 도메인 겸 DTO 클래스에 가깝습니다. 프론트에서 response에서 로직처리 없이 최대한 있는 그대로 값을 끌어오실 수 있게, 공수도 명시하면 좋을 것 같다고 생각했습니다.
안녕하세요! Jung & Jane입니다.
너무 늦게 PR을 보내게 되어 죄송합니다😭
데이터 통신 주기
pitch가 일어날 때는 프론트에서 정보를 캐싱해서 저장하고 있다가, 선수 교체를 주기로 서버에 요청을 보내고 해당 정보를 데이터베이스에 저장하는 형식으로 통신했습니다. 선수교체, 팀교체 등 전체적인 로직은 백엔드에서 처리하였습니다. 복잡한 로직을 처리하다보니 한 메서드 안에 관리해야 할 변수가 많아져 적절하게 메서드로 추출하는 등 리팩토링을 진행하지 못하였고, GameService안에 다소 난잡하게 메서드들이 나열되어 있습니다😖
데이터베이스 설계
프론트엔드에서 데이터를 받기 편한 상태로 DTO를 설계한 것과 분리하여 테이블 간 관계를 설정하였어야하는데 그렇게 하지 못했습니다😢 뒤늦게 team과 player를 1:n으로 맵핑해두고, 게임에 따라 바뀌는 match, inning, history, team_game_score, player_game_info를 inning을 중심으로 묶었으면 좋겠다는 생각이 들었지만, 이미 게임 로직이 다 짜여져있어 전체적인 수정이 필요해 시간이 부족하여 그렇게 설계하지 못했습니다.
배포