-
Couldn't load subscription status.
- Fork 177
[터틀] 지하철 정보 관리 - 인수테스트 미션 제출합니다. #33
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
Conversation
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으로 문의주세요!
피드백이 늦었는데 오늘은 빠른 피드백 드릴 수 있으니 빠르게 문의주셔도 됩니다!
| } | ||
|
|
||
| @GetMapping("/admin-line") | ||
| public ModelAndView adminLine() { |
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.
페이지에 대한 요청은 IndexController 쪽으로 옮기고 RestController에서는 페이지에 대한 처리를 빼주세요!
그후 @RequestMapping도 활용해보세요. 😉
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.
ViewController에서 페이지에 대한 요청을 처리하고, 각 RestController에서 해당하는 데이터를 반환해주도록 변경했습니다! @RequestMapping을 통해 중복 Path를 제거했습니다!
| String name = request.getName(); | ||
| String color = request.getColor(); | ||
| LocalTime startTime = request.getStartTime(); | ||
| LocalTime endTime = request.getEndTime(); | ||
| int intervalTime = request.getIntervalTime(); | ||
|
|
||
| Line line = new Line(name, color, startTime, endTime, intervalTime); | ||
| Line created = lineRepository.save(line); |
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.
service에서 해야할 작업들이 아닐까요? 그래서 지금 service에서 사용하지 않는 메서드들이 많네요.
new Line도 request에서 하나씩 값을 받아와서 만들지 말고 lineRequest.toLine()으로 변환해주면 좋을 것 같네요.
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.
오류 시에 메시지도 모두 클라이언트에서 검증 후 클라이언트에서만 알려주는데 api에서도 요청 실패시 잘못됐다는걸 클라이언트에 알려줄 수 있게 변경을 해보죠. 모든 곳을 완벽하게 하는걸 목표로 하지 말고 이곳에 한해서만 적용해보도록 해요.
이름이 중복됐을 때만 고려하고 중복 시 ResponseEntity.created가 아니라 badRequest를 발생시켜보세요.
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.
다음 사항을 반영했습니다!
- Controller의 로직들을 각각 해당하는 Service로 이동했습니다.
- RequestDto는
to~, ResponseDto는of~메소드로 변환하는 코드를 해당 클래스로 이동했습니다. - 이름에 대한 중복 검증 로직을 추가했습니다.
- RestControllerAdvice를 만들고, ExceptionDto를 통해 메시지를 클라이언트에 전달하도록 변경했습니다. 상태코드 또한 badRequest로 변경했습니다.
- RestControllerAdvice를 처음 사용해봤는데 적절하지 않은 부분 있으면 지적 부탁드립니다!
| } | ||
|
|
||
| @PostMapping("/lines") | ||
| public ResponseEntity<?> create( |
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.
?보다는 제대로된 타입을 적어주세요! 없는 경우에는 Void를 사용하시면 됩니다.
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.
리턴 타입을 명시적으로 변경했습니다!
| $stationNameInput.value = ""; | ||
| }; | ||
|
|
||
| const isValidStationName = stationName => { |
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.
검증 로직이 클라이언트에만 존재하는데 서버에도 존재해야하지 않을까요?
지금의 경우 동일한 api를 쓰는 다른 클라이언트가 생기게 된다면 모든 클라이언트에서 검증 로직을 추가해줘야 합니다.
사실 클라, 서버 둘 다 하는게 좋다고 생각하지만 클라쪽에서 누락하는 일이 발생하게 됐을 때도 있기 때문에 서버쪽이 더 우선적으로 존재해야 한다고 생각하는 편입니다. 더군다나 클라마다 동일한 검증 로직을 넣는 다는 보장도 없기 때문에 더더욱 api에서 챙겨야 한다고 생각합니다.
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 List<LineResponse> findAll() { | ||
| List<LineResponse> lineResponses = new ArrayList<>(); | ||
| List<Line> lines = lineRepository.findAll(); | ||
| for (Line line : lines) { | ||
| Set<Station> stations = stationRepository.findAllById(line.getLineStationsId()); | ||
| lineResponses.add(LineResponse.of(line, stations)); | ||
| } | ||
| return lineResponses; |
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.
딱 복잡하지 않는 선에서 스트림을 연습해보기 좋은 상황이네요.
LineResponse에서 Line과 Station을 받아 LineResponse를 생성하는 메서드를 만들어 주시고 Lines의 스트림을 통해서 LineResponse List를 만들어보세요.
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.
지금 보니 LineResponse.of(line, stations)은 존재하는군요! 그럼 더 수월하겠네요. 😉
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 Set<LineStation> findLineStation(long lineId) { | ||
| Line line = lineRepository.findById(lineId) | ||
| .orElseThrow(NoSuchElementException::new); | ||
| return line.getStations(); | ||
| } |
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.
해당 로직과 해당 로직을 테스트하는 코드를 제거했습니다!
| return ResponseEntity.created(url).body(lineStation); | ||
| } | ||
|
|
||
| @DeleteMapping("/lineStations/{lineId}/{stationId}") |
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.
지금처럼 아이디가 연속으로 있으면 Line, Station, LineStation 중 어떤 Id인지 구분이 안됩니다. 일반적인 상황에서 생각해보면 사용자는 lineId는 lineStations의 아이디라고 판단하기 쉽습니다.
/lines/{lineId}/stations/{stationId} 로 표현해주시는게 좋을 것 같습니다.
사용 예)
/lineStations/1/1
/lines/1/stations/1
그리고 REST API 제대로 알고 사용하기의 4-2. URI 설계 시 주의할 점 > 5) URI 경로에는 소문자가 적합하다. 참고해주세요!
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.
지금까지 URI 설계에 대해 생각해본적이 없었는데, 링크를 보면서 흉내내봤습니다! 감사합니다🙂
| int distance = request.getDistance(); | ||
| int duration = request.getDuration(); |
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.
넵 이번주차에 진행되는것같습니다 :)
src/main/resources/schema.sql
Outdated
| duration integer | ||
| ); | ||
|
|
||
| -- insert into LINE values(1, '1호선', 'bg-blue-700', '05:30', '23:30', 10, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); |
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.
편하게 초기 데이터들 세팅했네요. 👍
참고로 schema.sql은 스키마에 대한 정의만 작성하고 data.sql를 만들어서 거기로 옮겨주시는게 좋답니다! 😉
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.
data.sql을 생성하고 DML을 해당 파일로 이동했습니다!
| spring.h2.console.enabled=true | ||
| spring.h2.console.path=/h2-console | ||
|
|
||
| handlebars.suffix=.html | ||
|
|
||
| logging.level.org.springframework.jdbc.core.JdbcTemplate=debug |
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.
👍
- LineRequest에서 Line으로 변환하는 로직을 dto 내부로 이동
- StationRequest에서 Station으로 변경하는 로직을 dto 내부로 이동
- 컨트롤러에서 뷰로 LineStationResponse로 반환해주도록 변경 - default 데이터 제거 - 받아온 데이터로 보여주도록 변경
- 에러 발생시 alert로 메시지를 보여주도록 변경
|
이번에도 리뷰 감사합니다. :) 코멘트 달아주신 부분을 반영했습니다!
위 내용은 구현할때 생각 못했던 이슈가 있어 해결하고 마저 반영하겠습니다. 리뷰 해주신 URI 부분과 에러를 처리하는 부분(ControllerAdvice)을 처음 듣고 적용해봐서 미숙한데 부족한 부분 지적해주시면 감사하겠습니다 :) |
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.
몇가지 피드백 남겼습니다. 확인 부탁드려요!!
| if (name.isEmpty()) { | ||
| throw new IllegalArgumentException("값을 입력해주세요."); | ||
| } | ||
| if (name.contains(" ")) { | ||
| throw new IllegalArgumentException("공백 없이 입력해주세요."); | ||
| } | ||
| for (char c : name.toCharArray()) { | ||
| if (Character.isDigit(c)) { | ||
| throw new IllegalArgumentException("숫자 없이 입력해주세요."); | ||
| } | ||
| } |
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.
중복 확인 외에는 StationService가 아니라 Station에서 생성시에 확인하는게 좋지 않을까요? 😉
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.
StationName을 VO로 만들고 검증 로직을 한곳에서 관리하도록 변경했습니다 :)
|
|
||
| import wooteco.subway.admin.controller.advice.dto.ExceptionDto; | ||
|
|
||
| @RestControllerAdvice |
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.
try-catch, ExceptionHandler, ControllerAdvice의 차이가 무엇일까요?
스프링부트 : REST어플리케이션에서 예외처리하기
구현부분은 크게 참고할 부분은 없고 첫 문단과 전체적인 내용만 보시면 될 것 같아요. 😉
사실 ControllerAdvice을 주로 쓰는 편이지만 먼저 언급 드리지 않았는데 이유는 이런 스프링의 기능은 설정이나 애노테이션 정도로 손쉽게 사용이 가능해지는 부분이 있어 편하다면 편하지만 조금은 더 눈에 보이는 단계에서 처리를 해보고자 했답니다. 하지만 먼저 알게 되는 것도 좋은 방향이라고 생각합니다. 😉
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.
링크까지 주시고 감사합니다 :) @Responsetatus는 처음 접하네요!
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.
작성 시기도 어느정도 되어서 키워드 정도만 챙기시면 될 것 같아요.
대표적으로 RestControllerAdvice가 아닌 ControllerAdvice, RestController 두가지를 사용하고 있죠. 😉
| private LocalDateTime createdAt; | ||
| private LocalDateTime updatedAt; |
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.
지금 제거하실 필요는 없지만 사실 도메인 모델과 Reponse간에 1: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.
도메인 모델과 Reponse간에 1:1로 맞춰야 할 필요는 없답니다.
몰랐던 사실 알려주셔서 감사합니다 :) 두 필드 모두 제거했습니다.
| @@ -0,0 +1,16 @@ | |||
| package wooteco.subway.admin.controller.advice.dto; | |||
|
|
|||
| public class ExceptionDto { | |||
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.
ControllerAdvice는 현재 package 구조로는 지금 위치가 적당해보이지만 ExceptionDto는 엄염히 Dto인데 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에서 생성되고 Advice에서 에러 처리용도로만 사용되어서 그렇게 배치했는데, 적절하지 않았나보네요. dto 패키지로 이동했습니다.
| return line.getStations(); | ||
| } | ||
|
|
||
| @Transactional |
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.
@transactional 👍
Transactional을 붙였을 때와 안 붙였을 때의 차이가 무엇일까요?
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.
해당 메소드에서 문제가 생겨 처리가 중간에 끊겼을때 해당 트랜잭션을 롤백시켜 의도하지 않은 데이터 조작을 막을 수 있습니다. 그렇기 때문에 CUD 메소드를 Transactional로 만들면 적절하다고 생각합니다!
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.
Transactional을 물어보진 않겠지만 나중에 면접때 학습한/사용한 기술을 물어 볼 수 도 있어서 어느 정도 대답 해 둘 수 있으면 좋답니다. 😉
| } | ||
| }; | ||
|
|
||
| const lineStation = { |
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.
lineStation과 edge 중 하나로 통일하는게 좋을 것 같아요.
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.
모두 변경했다고 생각했는데 놓친 부분이 있었네요! 수정했습니다 :)
| Edge target = stations.stream() | ||
| .filter(st -> st.isEqualsWithStationId(stationId)) | ||
| .findFirst() | ||
| .orElseThrow(NoSuchElementException::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.
NoSuchElementException이 발생하는 곳은 메시지가 없어서인지 화면에서 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.
id를 직접적으로 사용하지 않는 사용자에게 NoSuchElement에 대한 커스텀 익셉션을 만들어 메시지를 전할 필요까진 없다고 생각이 드네요! ControllerAdvice에서 IllegalArgumentException만을 처리하도록 변경했습니다!
| Station preStation = stationRepository.findByName(request.getPreStationName()) | ||
| .orElseThrow(NoSuchElementException::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.
빈 구간에 역을 추가해보는게 볼가하네요. 기능 추가 부탁드립니다. 😢
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.
아래 사항을 변경해서 기능 추가했습니다!
- 출발역 다음역을 select box로 선택할 수 있도록 변경
- front에서 dataset으로 Id를 갖고있도록 변경
- id를 통해 Line을 조회하도록 변경
| @Mock | ||
| private StationRepository stationRepository; | ||
|
|
||
| private LineStationService lineStationService; |
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.
커밋하기 직전에 전체 테스트를 돌려보면서 이상이 없는지 확인 해보는게 좋답니다. 😉
그래서 빠른 테스트가 중요한데 테스트가 오래 걸린다면 자주 돌리는데 부담이 되고 전체 테스트를 해보는 주기가 길어지게 됩니다. 사실 이 부분에선 저도 개선해야 할 부분이 있네요. 😏
- lineStation의 이름을edge로 변경 - ExceptionAdvice에서 NoSuchElementException을 처리하지 않도록 변경 - LineResponse의 createdAt, updatedAt 필드제거
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.
고생하셨습니다! 👍 이번 단계는 여기서 머지하도록 할게요! 😉
감사합니다.