-
Notifications
You must be signed in to change notification settings - Fork 177
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 지하철 노선도 - 1,2단계] 연로그(권시연) 미션 제출합니다. #190
Conversation
- 중복되는 이름으로 노선 생성 - 존재하지 않는 노선 id 검색
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, 2단계 잘 구현해주신 것 같아요. 코드 보면서 깔끔하다는 느낌을 많이 받았습니다. 😃
README와 PR에 남겨주신 내용 덕분에 어떤 학습을 하셨는지 쉽게 파악할 수 있어서 좋았네요.
코멘트 몇 개 남겼는데 확인부탁드려요. 궁금하거나 얘기 나누고 싶은 부분은 언제든 편히 DM주시구요!
@@ -0,0 +1,40 @@ | |||
package wooteco.subway.domain; |
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.
entity vs domain
이번 단계까지는 entity와 domain을 분리할 필요가 없어서 분리하지 않았습니다! 다만 entity와 domain의 차이점에 대해서 잘 인지하지 못해서 나름 찾아보려고 노력했으나... DDD를 알아야한다 어쩌구 복잡한 설명들을 보고 더 헷갈리게 되었네요😵 또링이 추천하는 관련 책/자료 같은게 있으실까요...?🥺
entity, domain, vo.. 다양한 용어들의 등장으로 혼란스러우실 것 같아요. 🤯 저는 entity와 domain이 명확하게 분리되는 개념이라고 생각하지 않아요. https://doing7.tistory.com/79 이 글을 읽어보시면 조금 더 감이 잡히실거예요. DDD라는 단어는 블러처리하고 봐주셔도 좋아요 :)
return ResponseEntity.created(URI.create("/stations/" + newStation.getId())).body(stationResponse); | ||
} | ||
|
||
@GetMapping(value = "/stations", produces = MediaType.APPLICATION_JSON_VALUE) |
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.
Station에 전체 조회 시 produces = MediaType.APPLICATION_JSON_VALUE라는 옵션이 있었는데요 (직접 작성한 로직이 아닌 이미 만들어져있던 API임) 추가하나 하지않으나 JSON 형태로 return하는 것 같아 불필요한 옵션이라고 판단, 해당 옵션을 삭제했습니다.
넵 좋습니다!😃 produces, consumes를 추가하게되면 주고 받는 데이터 형식을 명시적으로 확인 및 필터링 할 수 있어서 이점이 있지만, 말씀하신대로 여기서 굳이 가지고 있을 필요는 없어보이네요! (참고로 전 파일과 같이 특이한 형태의 데이터를 주고 받을 때 붙여주고 있어요.)
public Line(String name, String color) { | ||
this.name = name; | ||
this.color = color; | ||
} | ||
|
||
public Line(Long id, String name, String color) { | ||
this.id = id; | ||
this.name = name; | ||
this.color = color; | ||
} |
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.
생성자에 중복된 코드가 보여요! this를 이용해 중복을 제거해볼까요?
public boolean matchId(Long id) { | ||
return this.id.equals(id); | ||
} |
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.
사용하지 않는 메서드는 제거해주세요.
@PutMapping("/{id}") | ||
public void updateLine(@PathVariable Long id, @RequestBody LineRequest lineRequest) { | ||
lineService.update(id, lineRequest); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
public void deleteById(@PathVariable Long id) { | ||
lineService.deleteById(id); | ||
} |
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.
저라면 수정된 결과를 응답으로 받아야한다면 200, 응답이 불필요하다면 204 no content로 보낼 것 같아요
또는 화면 새로 고침을 요구하기 위해 205 까지도 사용할 수 있을 것 같습니다!
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.
그렇군요! 좋아요😃 보통 PUT에 대한 응답은 200이나 201로 많이 내려요. 리소스가 생성(혹은 변경)되었다는 의미로 200보다는 조금 더 구체적인 201을 사용하기도해서, 연로그의 생각을 물어봤어요. 200도 좋습니다!
@PostMapping | ||
public ResponseEntity<LineDto> createLine(@RequestBody LineRequest lineRequest) { | ||
Line newLine = lineService.save(lineRequest); | ||
LineDto lineResponse = LineDto.from(newLine); | ||
return ResponseEntity.created(URI.create("/lines/" + newLine.getId())).body(lineResponse); | ||
} |
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.
자원 생성에 대해 201 응답 + URI까지 잘 담아주셨네요! 💯
혹시 body는 왜 넣어주고 있나요~?
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 문서를 따른 내용입니다😄
@ControllerAdvice | ||
public class SubwayControllerAdvice { | ||
|
||
@ExceptionHandler | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public void handleException(IllegalArgumentException e) { | ||
e.printStackTrace(); | ||
} | ||
} |
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에서 발생하는 예외 처리를 위해 ControllerAdvice를 따로 분리하셨군요 👍
하지만 예외가 발생하면 stackTrace만 찍을 뿐 어떤 처리도 하지 않고 있네요. 이런 상황에서 사용자는 어떤 경험을 하게 될까요?
또, SQL 예외나 NPE와 같은 예외는 어떻게 처리할 수 있을까요?
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.
예외가 발생하는 경우 프런트 쪽에서 별도로 처리해둔 코드를 알지 못해 Http 상태 코드만 설정해두었습니다😂
전에 프런트까지 직접 구현했을 때는 에러의 메시지를 보내주었던 것 같아요
SQL이나 NPE 같은 경우에는 리팩터링하며 수정하겠습니다!
@DisplayName("id 를 이용하여 노선을 삭제한다.") | ||
@Test | ||
void deleteLineById() { | ||
//given | ||
String id = String.valueOf(savedId1); | ||
|
||
//when | ||
ExtractableResponse<Response> response = RestAssured.given().log().all() | ||
.when() | ||
.delete("/lines/" + id) | ||
.then().log().all() | ||
.extract(); | ||
|
||
//then | ||
assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); | ||
} |
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에 대한 응답을 확인하는 것도 좋지만, 다시 조회 했을 때 해당 id를 가진 노선이 없다는 걸 확인해보면 더 정확한 테스트가 될 것 같아요!
2. Spring Bean & Spring JDBC 적용하기 | ||
- `@Controller`, `@Service`, `@Repository` 적용 | ||
- 예외 `@ExceptionHandler`로 처리 | ||
- JdbcTemplate는 `NamedJdbcTemplate` 사용 |
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.
@Controller
, @Service
, @Repository
적용
의존 관계를 직접 주입하며 크고 작은 에러를 많이 만나다 테스트 코드를 위한 메서드를 생성하는 상황(..)까지 이르렀는데 컴포넌트를 활용할 수 있게 되니 훨씬 편리함을 느꼈습니다
컴포넌트 어노테이션을 왜 사용하느냐 라고 누가 묻는다면 그 이유 중 하나로 편리한 의존 관계 주입
이라고 답할 것 같아요!👍
예외 @ExceptionHandler
로 처리
예외를 위해 controller의 모든 메서드에서 try - catch를 사용해야했습니다
반환 값도 모든 것을 ResponseEntity를 활용해야했고요 (개인적으로는 더 가독성이 좋다고 생각해 @ResponseStatus
를 사용하고 dto를 반환하는 것을 선호합니다)
예외 발생하는 부분을 공통적으로 처리할 수 있는게 편리했어요😄
JdbcTemplate는 NamedJdbcTemplate
사용
여러가지 JdbcTemplate 중에서 NamedJdbcTemplate을 사용한 이유는 파라미터의 이름을 지정할 수 있었기 때문입니다
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.
크 공유 고마워요! 다양한 걸 느끼셨군요.😎
앞으로 더 편리하고 신기한 기술들을 마주치게 될텐데, 그때도 지금처럼 이 기술이 무엇을 해결하기 위해 등장했고 어떤 점이 편리한지 등을 생각하면서 사용하면 기술들을 더 잘 사용하실 수 있을거예요 👍
- 상태 코드가 아닌 id 목록의 일치하는지 검사
- 불필요/필요한 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.
연로그! 리뷰 잘 반영해주셨네요. 🚀
몇 가지 코멘트 남겼는데 확인해보시고 다음 단계 구현하면서 반영해주세요.
이번 단계는 여기서 마무리할게요. 수고 많으셨어요 :)
다음 단계도 화이팅입니다!
### 1단계 기능 목록 | ||
> 데이터 형식은 [API 문서](https://techcourse-storage.s3.ap-northeast-2.amazonaws.com/d5c93e187919493da3280be44de0f17f#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.
🙇🏻♀️
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
@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.
👍
@ExceptionHandler | ||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||
public void handleException(Exception e) { | ||
logger.error(EMPTY_STRING, e); | ||
} |
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.
코드상 의미있는 메시지를 던져준건 IllegalArgumentException 뿐입니다!
만약 의미있는 메시지를 던지는 Exception을 던지면 그때 가서 별도로 처리해줄 것 같아요~
Exception은 그 외에 예상치 못한 예외 상황일텐데 이 메시지들까지 사용자에게 던져도 괜찮은가?에 대한 의문이 찜찜해서 따로 넘겨주지는 않았습니다!
2. Spring Bean & Spring JDBC 적용하기 | ||
- `@Controller`, `@Service`, `@Repository` 적용 | ||
- 예외 `@ExceptionHandler`로 처리 | ||
- JdbcTemplate는 `NamedJdbcTemplate` 사용 |
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.
크 공유 고마워요! 다양한 걸 느끼셨군요.😎
앞으로 더 편리하고 신기한 기술들을 마주치게 될텐데, 그때도 지금처럼 이 기술이 무엇을 해결하기 위해 등장했고 어떤 점이 편리한지 등을 생각하면서 사용하면 기술들을 더 잘 사용하실 수 있을거예요 👍
private void validDuplicatedName(String name) { | ||
if (lineDao.countByName(name) > 0) { | ||
throw new IllegalArgumentException(DUPLICATED_NAME_ERROR_MESSAGE); | ||
} | ||
} |
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.
중복 확인을 위해 count 쿼리를 사용하고 있는데, 더 좋은 방법은 없을까요? https://jojoldu.tistory.com/516 참고하면 좋을 것 같아요.
@PutMapping("/{id}") | ||
public void updateLine(@PathVariable Long id, @RequestBody LineRequest lineRequest) { | ||
lineService.update(id, lineRequest); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
public void deleteById(@PathVariable Long id) { | ||
lineService.deleteById(id); | ||
} |
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.
그렇군요! 좋아요😃 보통 PUT에 대한 응답은 200이나 201로 많이 내려요. 리소스가 생성(혹은 변경)되었다는 의미로 200보다는 조금 더 구체적인 201을 사용하기도해서, 연로그의 생각을 물어봤어요. 200도 좋습니다!
import wooteco.subway.domain.Station; | ||
|
||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||
@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
어노테이션이 등장했네요. ㅎㅎ 연로그는 이 어노테이션을 어떤 문제를 해결하기 위해 사용하셨나요? 또, 문제를 해결할 수 있는 다른 방법은 무엇이 있을까요?
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에 반영되지 않도록 하기 위함입니다!
테스트 전용 테이블을 따로 만들거나 테스트가 끝날때마다 데이터를 수동으로 지워주는 방법이 있을 것 같아요 :)
// then | ||
assertThat(before.size() - 1).isEqualTo(after.size()); | ||
} | ||
} |
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.
@DisplayName("id 를 이용하여 노선을 삭제한다.") | ||
@Test | ||
void deleteLineById() { | ||
//given | ||
String id = String.valueOf(savedId1); | ||
List<Long> expectedIds = selectLines(); | ||
expectedIds.remove(Long.parseLong(id)); | ||
|
||
//when | ||
ExtractableResponse<Response> response = RestAssured.given().log().all() | ||
.when() | ||
.delete("/lines/" + id) | ||
.then().log().all() | ||
.extract(); | ||
|
||
//then | ||
assertThat(expectedIds).isEqualTo(selectLines()); | ||
} | ||
|
||
private List<Long> selectLines() { | ||
ExtractableResponse<Response> response = RestAssured.given().log().all() | ||
.when() | ||
.get("/lines") | ||
.then().log().all() | ||
.extract(); | ||
|
||
return response.jsonPath().getList(".", LineResponse.class).stream() | ||
.map(LineResponse::getId) | ||
.collect(Collectors.toList()); | ||
} |
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.
정확한 검증 👍
selectLines()
라는 이름보다는 getAllLines()`` findAllLines()
와 같은 이름이 명확할 것 같다는 생각이 드네요 ㅎ_ㅎ 네이밍은 연로그의 취향에 맞춰 선택해주세요~
Map<String, String> parameter = new HashMap<>(); | ||
String name = "분당선"; | ||
parameter.put("name", name); | ||
parameter.put("color", "bg-red-600"); |
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.
Map이 아닌 객체를 만들어 바로 넣을 수 있어요 :) Request객체 이용하면 편하겠네요 ㅎㅎ
안녕하세요 또링! 이번 미션 잘 부탁드립니다😆
1단계 기능 목록
2단계 기능 목록
@Controller
,@Service
,@Repository
적용@ExceptionHandler
로 처리NamedJdbcTemplate
사용고민했던 포인트
1단계 요구사항 - 컴포넌트/빈 없이 의존 관계를 주입하기
첫번째 시도
: dao의 데이터가 공유되어야 하므로 필드에 static 키워드 사용두번째 시도
:@BeforeAll
을 이용해 초기 데이터 주입@BeforeAll
는 static 키워드가 필수entity vs domain
produces
produces = MediaType.APPLICATION_JSON_VALUE
라는 옵션이 있었는데요 (직접 작성한 로직이 아닌 이미 만들어져있던 API임) 추가하나 하지않으나 JSON 형태로 return하는 것 같아 불필요한 옵션이라고 판단, 해당 옵션을 삭제했습니다.