-
Notifications
You must be signed in to change notification settings - Fork 122
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
경북대 BE_김동현_1주차 과제(3단계) #201
base: donghyuun
Are you sure you want to change the base?
Conversation
기존 컨트롤러의 역할을 서비스 클래스를 사용하여 MVC 패턴에 맞게 역할 분담함
기존 방식(코드)에서 application.properties 사용으로 변경
(부산대 BE 정재빈님의 코드를 참고하여 기능을 추가하였습니다) 예외 처리가 거의 처음이다 보니, 커밋 규모가 커진 것 같습니다.. 응답 body 에 담기는 ResponseDTO 종류 1. SimpleResultResponseDto - 커스텀 응답 코드, 성공 메시지 포함 2. ResultResponseDto - 커스텀 응답 코드, 성공 메시지, "데이터" 포함 3. ErrorResponseDto - 커스텀 응답 코드, 실패 메시지 포함
@MangKyu 멘토님! 안녕하십니까. 저는 경북대학교 BE 김동현입니다. |
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분이 계시는데, 이 부분 때문에 혼란이 와서 제가 리뷰가 없는 것으로 착각했네요.....
그래서 더욱 성심성의껏 PR 올려주신 것 확인했고, 전반적으로 잘 작성해주셨는데 몇 가지 고치면 좋을 것 같아서 코멘트들 남겨두었어요ㅎㅎ
확인해보시고, 필요한 부분 반영해주시면 좋을 것 같습니당! 파이팅입니다!! 🔥🔥🔥
(앞에 1, 2단계 별도로 올려주신 PR도 여기에 한번에 달았으니 닫으셔도 될 것 같습니당 🙏 )
추가로 질문 주신 부분들에 대한 답변을 간단히 남겨드릴께요!
- step1
- Q1: Service는 비즈니스 로직을 처리하는 레이어입니다! 반면에 ResponseEntity는 비즈니스 로직이 아닌 컨트롤러를 통해 전달되는 응답 스펙에 가깝습니다! 따라서 Service에서 객체 만을 반환하는 것이 일반적이라고 볼 수 있습니다ㅎㅎ
- Q2: 해당 부분은 이렇게도 할 수 있고, 저렇게도 할 수 있습니다. 제 개인적으로는 body의 값을 커스텀해야 하는 경우라면 모든 팀 간의 일원화된 스펙을 위해 사용하는 것이 좋은 것 같습니다! 그렇지 않은 경우라면 굳이 커스텀을 하지 않습니다!
- Q3: 제 개인적으로는 도메인 끼리 응집도 있는 구조를 선호합니다! 그래야 product 관려된 기능을 찾을 때, product라는 패키지 하위에서 컨트롤러와 서비스 등에 손쉽게 접근할 수 있기 때문입니다! 저도 여러 번 시행 착오를 해보았는데, 패키지 별로 나누고 하위에 계층으로 나누는 것이 작업에 수월했습니다ㅎㅎ
- Q4: 제 개인적으로는 javadoc이 없으면 안되는 경우에 작성하지 않습니다! 주석 관련해서는 리뷰 코멘트에 남겨드렸으니 확인 부탁드립니다!
- Q5: 현재의 테스트 코드는 구현과 강결합 되어 있고, 설정이 중복되는 등의 문제가 있는 것 같습니다! 따라서 조금 더 개선의 여지가 있는 테스트 코드라고 볼 수 있습니다! 제 개인적으로는 컨트롤러로 들어오는 비즈니스 입력 흐름(유스케이스)은 통합테스트, 도메인에 대한 테스트는 단위 테스트로 작성하는 것을 선호합니다! 테스트 관련해서도 공부해야 하는 내용이 한바닥이라서 다 적기가 어려운 부분이 있네요ㅎㅎ....
- step2
- Q1: 최근에는 FE 서버가 분리되어 있어서 API 서버만 개발하는 경우가 많습니다! 하지만 어드민과 같은 경우에는 서버 개발자가 SSR로 하는 곳도 많이 있어서, 팀마다 다르다고 볼 수 있을 것 같습니다ㅎㅎ
- Q2: 올바르게 진행하신 것 같습니다! 약간 더 디벨롭하면, ProductRepository 인터페이스와 그 구현체로 InmemoryProductRepository, JpaProductRepository 요렇게 작업을 진행할 수 있을 것 같습니다ㅎㅎ 그래서 구현체만 JpaProductRepository 로 갈아끼우면 다른 곳들은 수정할 필요가 없게 작업할 것 같습니다!
- step3
- Q1: 일반적으로 클라이언트가 성공과 실패를 구분하는 가장 기본이 되는 값이 HTTP Status입니다! 2xx이면 성공, 4xx 또는 5xx이면 실패로 간주할 것이기 때문에, 동현님이 보내주신 400 응답을 보고 실패 여부를 확인하게 됩니다ㅎㅎ
질문 주신 부분에서 답변을 충분하게 느끼지 못한 부분이 있다면 편하게 말씀해주세요ㅎㅎ
@@ -24,6 +24,9 @@ dependencies { | |||
runtimeOnly 'com.h2database:h2' | |||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | |||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | |||
|
|||
implementation 'org.springframework.boot:spring-boot-starter-validation' |
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.
오 validation 기능을 활용하셨군요 좋습니당! 👍 저도 좋아합니다!
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
@RequestMapping(value = "/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.
별건 아니지만 줄일 수 있는 부분은 줄이는 것이 좋은 것 같아요ㅎㅎ
@RequestMapping(value = "/api") | |
@RequestMapping("/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.
넵 반영하겠습니다!
|
||
private final ProductService productService; | ||
|
||
@Autowired |
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개인 경우에는 @Autowired 빼셔도 됩니다! 알고 계시겠지만 혹시나 하여 남깁니다! 🙏
@Autowired |
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.
멘토님 말씀 듣고 생각해보니 추상화 시키는 것이 좋은 것 같습니다!
* | ||
* @param productDTO | ||
*/ | ||
public void postProduct(ProductDTO productDTO) { |
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 void postProduct(ProductDTO productDTO) { | |
public void createProduct(ProductDTO productDTO) { |
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 가 당연하다고 생각했었네요! 감사합니다 ㅎㅎ
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.
최근에 목적조직(개발자, 기획자, 데이터분석가, 디자이너 등이 하나의 팀을 꾸려 특정한 목적을 달성하기 위해 일하는 조직)으로 팀구성을 하는 회사들이 많아짐에 따라 특히 업무에 대한 소통 그리고 그 소통을 그대로 코드에 녹여내는 중요성이 많이 중요해지는 것 같네요ㅎㅎ
@Component | ||
public class Validation { | ||
|
||
public void validateProduct(ProductDTO productDTO) { |
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.
여기도 product 외에 다른 도메인이 생긴다면, 서로 다른 도메인이 해당 클래스에 결합될 여지가 있네요ㅎㅎ
그리고 아래 구현을 보니 입력에 대한 유효성 검사인 것 같네요! 해당 부분을 위한 포스팅 남겨드립니다!
해당 내용을 적용하면 Validation
클래스는 없어도 될 것 같네요:)
public List<Product> getProducts() { | ||
String sql = "SELECT * FROM product ORDER BY ID ASC"; | ||
|
||
List<Product> products = jdbcTemplate.query(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.
요 케이스에는 null이 안내려오지 않나요~? 빈 값으로 올 텐데, 불필요한 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.
값이 없을때도 null 이 아닌 빈 리스트 인것을 잊었습니다. 반영하겠습니다.
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
|
||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) |
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.
열심히 테스트를 작성해주신 부분이 눈에 보이네요ㅎㅎ 고생많으셨습니다!
|
||
//then | ||
assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); | ||
Product product = objectMapper.convertValue(responseEntity.getBody(), Product.class); |
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.
이렇게 테스트 json 응답을 역직렬화하여 검증하는 것은 테스트와 프로덕션 코드 간의 강결합을 유발하는 요인이 될 수 있습니다ㅎㅎ json path를 이용하는 방법도 있으니 확인해보시면 좋을 것 같네요!
String imageUrl = "testImageUrl.com"; | ||
|
||
ProductDTO productDTO = new ProductDTO(name, price, imageUrl); | ||
String url = "http://localhost:" + port + "/api/products"; |
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 호출 주소를 생성해주는 부분이 비효율적이고 바람직하지 않다고 느끼셨을 것 같아요!
TestRestTemplate은 사용성이 떨어질 뿐만 아니라 maintenance-mode로 진입한 라이브러리이다 보니 다른 도구를 찾는 것도 좋을 것 같아요! 제 개인적으로는 RestAssured를 선호합니다!
<리뷰 반영 사항> (서비스 또는 에러 핸들러에서) ResponseMaker 가 응답 객체를 생성할 때, Enum 을 통해 여러 클래스를 거쳐서 응답을 생성했던 것을, 매개변수로 개발자가 상태 코드와 메시지를 아서 응답 객체를 생성하는 식으로 변경했습니다. 성공 시에 보내는 응답 객체에, 말씀 주셨던 ResponseEntity.ok 등을 공통으로 사용할까도 생각해보았는데, 개발자가 상태 코드와 메시지를 상황별로 주는게 개발자도 이해하기 좋지 않을까.. 라는 생각으로 상태코드와, 메시지를 직접 입력할 수 있도록 했습니다. 테스트 관련 부분은 이번 주차 과제에 적용시켜보겠습니다! 질문 Q2. NO_CONTENT 적용을 하면 응답 본문에 데이터가 들어갈 수 없다고 알고 있습니다. |
@donghyuun 리뷰 잘 반영해주셔서 감사합니다! 질문 주신 부분에 대해서도 답변드리겠습니당:) Q1일반적으로 현업에서도 Enum을 통해 에러 코드를 정의합니다! 이를 통해 컨트롤러나 서비스에서 Enum의 코드만 넘겨주면 ExceptionHandler에서 내부의 Enum 데이터로 에러를 핸들링할 수 있기 때문입니다! 다만 에러 코드 처리를 조금 더 고도화해서 사용하기도 합니다! 복잡성을 느끼셨던 부분이 어떤 부분일까요~? 조금 더 부연설명 해주시면 감사드리겠습니다ㅎㅎ 저도 개인적으로 궁금해서요! Q2과거에는 이러한 메시지 처리 역시 서버에서 처리하는 경우가 많았습니다만, 최근에는 FE 서버들도 분리됨에 따라 서버에서는 요청의 성공/실패만 전달해도 충분하지 않나 생각합니다ㅎㅎ 하지만 앞서 말씀드렸든 팀원들 그리고 프로젝트 성향 등에 따라 맞춰가는 것이 중요합니다! 역시나 제가 맡은 플젝에서도 상태만으로 처리하는 플젝도 있는 반면, 띄워줄 메시지까지 서버에서 만환하는 플젝도 있습니다ㅎㅎ 소프트웨어 개발에서 정답이 없다보니 |
[Enum 을 사용했을 때 복잡성을 느꼈던 부분]
위와 같이 사용했을 때, 모든 API 별 응답 객체의 Enum 값(상태코드, 메시지)이 달랐고, 관련 응답 코드 및 메시지를 확인하기 위해 다시 Enum 을 들어가서 하는게 복잡하다고 생각했습니다. 그래서 응답 객체에 개발자가 직접 상태코드와 메시지를 넘겨주면 응답 객체를 만들 때 Enum 을 들어가서 확인하는 단계를 생략할 수 있다고(응답 생성 프로세스를 간소화) 생각했고, 또 이렇게 하는게 편하다고 느꼈습니다.
그런데 오늘 모각코를 하면서 조원들의 코드를 보니 Enum 에 API 별로 값을 모두 생성하는게 아니라, 요청 오류, 값 존재 X 등을 기준으로 Enum 을 생성하는 것을 보았습니다.
리뷰에서 말씀 주신 내용 중에 클라이언트는 상태 코드를 가지고 성공/실패 여부를 판단하기 때문에 메시지의 중요도가 협의에 따라 낮아질 수도 있다고 하셨었는데, 이러한 맥락에 의하면 다양한 API 상황에 대해서 이 정도의 Enum 값을 통해 대처할 수도 있겠다(문제되지 않겠다) 라는 생각이 들었습니다. 따라서 추후 다시 Enum 사용을 해보려합니다. 답변 주셔서 감사합니다! |
Enum을 사용하는 기준이 조금 달랐던 것 같네요ㅎㅎ 계속해서 이런 식으로 디벨롭하면서 경험치가 쌓이는 부분이 너무 좋아보이네요! 화이팅입니다! 👍 |
3단계를 진행하면서, 처음에는 수행 성공/실패에 맞는 HttpStatus 코드와 body 에 문자열을 담아서 보냈었습니다.
그러나 body 에 성공/실패 응답 객체를 각각 만들어서 통일성 있는 응답이 필요해보였습니다.
공통 응답 성공/실패 객체를 사용해본 적이 없어 타 쿠키분들의 코드를 참고하여 공부 후, 작성하게 되었습니다.
이러한 과정 속에서, 실패 객체를 예외 발생 시 응답에 담기 위해 커스텀 Exception 과 이를 관리하기 위한 @RestControllerAdvice 핸들러를 사용하였습니다.
[질문]
클라이언트가 요청을 보내면 성공 또는 실패 응답 객체를 받게 됩니다. 그러나 클라이언트는 해당 객체를 받을 때는 성공 객체인지, 실패 객체인지 알 수 없습니다.
두 객체는 내부적으로 속성의 개수, 속성명 등이 다를텐데, 클라이언트가 어떻게 해당 응답이 성공 or 실패 응답 객체인지 판단하고, 어떻게 객체 내부의 필드값을 사용할 수 있는지 궁금합니다.
과제는 미리 제출하였으나 구글 폼 제출이 늦었습니다. 죄송합니다. 다음부터 주의하겠습니다.