-
Notifications
You must be signed in to change notification settings - Fork 46
[BE] TEAM-11 Jane & Jung PR드립니다. #34
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
base: TEAM-11
Are you sure you want to change the base?
Conversation
- Team와 Player이 History와 각각 1:N관계를 맺도록 클래스에 List<History> 추가
DB 설계 및 mock API 구성
[BE] Controller 메서드 구현
- WebConfig에서 전역에 cross origin을 적용하는 방법에서 컨트롤러별로 적용하는 방식으로 변경
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.
우선 프로젝트를 일정에 맞게 마무리하고 기능 구현을 완료한 것은 칭찬합니다. 👍 💯 수고 많으셨습니다.
그러나 세부적인 구현 면에서 보면 다소 실망스러운 감이 있습니다.
특히 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.
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.
선수가 교체될때마다 업데이트된 정보를 반영해 (선수 히스토리, 공수교대, 득점 등) 백엔드에서 화면에 필요한 모든 정보를 response해주기로 협의했습니다. 필요한 모든 정보를 하나로 묶기 위한 DTO클래스라고 생각해주시면 좋을 것 같습니다 ㅜㅜ
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor |
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.
여기 에 따르면 같은 타입의 필드 변수의 순서가 바뀔 경우, @AllArgsConstructor로 생성자 매개변수의 순서도 자동으로 바뀌어 문제가 발생할 수 있다고 합니다.
애노테이션 사용없이 직접 생성자를 사용하거나, 빌더를 쓰면 좋을 것 같은데 생각하지 못했던 것 같습니다. 지적 감사합니다!
| this.pitchCount = player.getPlayerGameInfo().getPitchCount(); | ||
| this.plateAppearances = player.getPlayerGameInfo().getPlateAppearance(); | ||
| this.hits = player.getPlayerGameInfo().getHits(); |
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.
생성자에 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모든 필드가 null 이어도 괜찮을까요? 흠...
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.
잘못 넣은 것 같습니다;;ㅎㅎ JPA때랑 헷갈렸는지, 왜 추가해줬는지 모르겠네요. 심지어 Entity도 아니고 DTO인데! 바로 삭제하겠습니다
| for(Player player: team.getPlayerList()) { | ||
| playerGameScore.add(new PlayerGameScoreDTO(player)); | ||
| } |
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.
List 와 같은 컬렉션 성격의 변수를 다루는 경우 원소를 따로 빼서 .add() 연산을 하는 것보다는, 리스트 자체를 통째로 갈아끼우는 구현이 사이드 이펙트를 방지하는 방향입니다.
Player 를 Team 에서 하나씩 꺼내서... DTO 로 변환한 후에 list 에 하나씩 밀어넣는 것보다, Team 이 PlayerGameScoreDTO 리스트를 통째로 딱 내려주는게 더 좋을 텐데요.
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.
리스트 자체를 통째로 갈아끼우는 구현 -> 이렇게 하고 싶었는데 더 좋은 방법을 고민해보겠습니다.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic 안에 List를 넣어도 됩니다. 배열을 써야 할 특별한 이유가 있는게 아니라면, 그렇게 개선해보시는게 좋겠어요.
public ResponseEntity<List<TeamScoreDto>>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.
배열로 하니까 add 과정이 없어 코드 두 줄을 절약할 수 있어 저렇게 했는데, list가 컨벤션인가 보네요! 지금은 배열에 원정팀 스코어, 홈팀 스코어 두개뿐이므로 문제가 없어 보일 수 있는데, 나중에 배열에 다른 값들이 추가된다면 리스트가 더 유연하게 대응할 수 있을 것 같습니다. 몰랐는데 감사합니다!
|
|
||
|
|
||
|
|
||
| public class Constants { |
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.
상수를 분리해서 선언해둔 것은 좋습니다만...
아래 필드들을 보니, 별도의 enum으로 분리하여 관리해야 할 항목들이 좀 보이네요.
예를 들어 STRIKE, BALL... 의 경우, 플레이의 결과물이므로 분리되는 게 좋았을 것 같고,
PITCHER, HITTER 와 같은 것도 선수의 포지션에 관한 enum일 거고요.
설계상에 고려가 필요하지 않았나 생각됩니다.
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.
검색했더니 JDBC에서 enum을 지원하지 않는다고 해서 DB에 저장할 방법이 없는 줄 알았습니다! 잘못 알았네요. 처음에 enum을 만들었다가 에러가 떠서 jdbc연동문제인줄 알고 다시 지웠었는데, 더 공부해보고 다른 분들 코드도 많이 참고할 걸 그랬습니다. enum으로 유연한 설계 해보겠습니다! 감사합니다.
| @Id | ||
| @JsonIgnore | ||
| private Long id; | ||
| private String actionName; |
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.
enum 으로 관리했으면 더 유연했겠죠.
|
|
||
| private int out; | ||
| private int inningNumber; | ||
| private String role; |
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.
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을 중심으로 묶었으면 좋겠다는 생각이 들었지만, 이미 게임 로직이 다 짜여져있어 전체적인 수정이 필요해 시간이 부족하여 그렇게 설계하지 못했습니다.
배포