Skip to content
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

[1단계 - 상품 관리 기능] 제이미(임정수) 미션 제출합니다. #237

Merged
merged 42 commits into from
May 3, 2023

Conversation

JJ503
Copy link
Member

@JJ503 JJ503 commented Apr 27, 2023

안녕하세요 춘식!
레벨 1 체스 미션에 이어 또 뵙게 되어 반갑습니다 ☺️

이번 미션을 진행하며 의문점들과 춘식의 의견이 궁금한 점들이 있습니다.
해당 부분들은 코드에 코멘트로 달아 두었습니다.
이번 미션에서 특히 고민한 부분은 요청에 대한 응답과 테스트 코드입니다.

이 외에도 좀 더 고민해봐야 할 사항 및 여러 피드백 부탁드립니다!

이번 리뷰도 잘 부탁드립니다 ☺️
감사합니다!

JJ503 and others added 30 commits April 25, 2023 17:53
- index.html에 상품 정보에 맞게 thymeleaf 변경

Co-authored-by: dooboocookie <[email protected]>
- admin.html에 상품 정보에 맞게 thymeleaf 변경

Co-authored-by: dooboocookie <[email protected]>
- admin.html 상품등록 모달창 input 태그 name속성 지정
- admin.js axios 요청 설정

Co-authored-by: dooboocookie <[email protected]>
- item 목록을 응답한다.

Co-authored-by: dooboocookie <[email protected]>
- 요청 DTO 추가
- 저장 DTO 네이밍 변경

Co-authored-by: dooboocookie <[email protected]>
- {id}의 정보를 수정하는 기능을 한다.
- admin.js axios 요청 설정 추가

Co-authored-by: dooboocookie <[email protected]>
- {id} 정보를 삭제하는 기능을 한다.
- admin.js axios 요청 설정 추가

Co-authored-by: dooboocookie <[email protected]>
- {id} 정보를 삭제하는 기능을 한다.
- admin.js axios 요청 설정 추가

Co-authored-by: dooboocookie <[email protected]>
- 하나의 아이템 조회 기능 추가

Co-authored-by: dooboocookie <[email protected]>
- initializeTestDb.sql로 테스트마다 테이블 DROP 후 CREATE하도록 변경

Co-authored-by: dooboocookie <[email protected]>
- Item을 도메인으로 고려
- ItemResponse DTO를 생성

Co-authored-by: dooboocookie <[email protected]>
JJ503 and others added 6 commits April 27, 2023 11:21
- 요청과 컨트롤러 메서드 매핑 테스트
- 컨트롤러 메서드 반환값 테스트

Co-authored-by: dooboocookie <[email protected]>

@Controller
@RequestMapping("items")
public class ItemController {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POST / PUT 요청에 대한 응답 시 프론트에서는 별다른 응답 값을 기대하지 않을 것입니다.
그래서 상태코드와 이동할 주소만 전달하도록 했습니다.
그런데 이와 같은 경우에도 응답 메시지가 필요하다고 생각하시는지 궁금합니다.

만약 필요가 없다고 생각하신다면, ResponseEntity와 같이 Raw 데이터가 반환되게 되는데 이에 대해 어떻게 생각하시는지 궁금합니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

또한, 현재 각 api에서 반환되는 상태 코드가 적절한지 궁금합니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프론트에서 요청에 대해 어떤 응답을 기대하는지는 프로젝트 또는 팀 정책마다 달라질 수도 있습니다. 하지만 이번 미션에서는 admin.js에서 업데이트 등의 요청을 받으면 리로드나 리다이렉트가 되고 있는 구조라 따로 필요한 응답 값이 없기 때문에, 상태코드와 주소(이것도 옵션이긴 합니다)만 전해줘도 됩니다. 🙂

ResponseEntity와 같은 Raw 데이터는 무슨 의미일까요? 만약 body가 비어있는 걸 의미한 거라면, ResponseEntity는 HttpEntity를 상속받는 클래스라서 기본적으로 HttpStatus, HttpHeaders, HttpBody를 전부 포함하고 있습니다. HttpBody에 담기는 데이터가 없더라도 status와 헤더로 성공/실패 여부, 인증 인가 상태 등을 확인할 수 있기 때문에 객체를 바로 보내는 것과 차이가 있다는 점 참고바랍니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

응답에 대한 얘기가 나온 김에 @Controller@RestController의 차이를 알아보는 것도 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변 감사합니다!
Raw 데이터에 대해 좀 더 고민해 본 결과 ResponseEntity 타입으로 Void도 가능하다는 것을 알게 되었습니다. 그래서 제네릭 타입을 Void로 수정해 두었습니다.
제네릭 타입을 설정해 두는 것이 좀 더 명확하게 의미를 전달할 수 있을 것이라 판단해 Void로 수정했습니다.

@Controller@RestController의 차이는 @RestController@Controller+@ResponseBody라고 알고 있습니다.

그런데 여기서 궁금한 점은 반환을 ResponseEntity로 하기에 사실상 @ResponseBody가 필요하지 않으며 @Controller로도 충분히 목적은 달성하는 상태입니다.
하지만 ItemController의 경우 CRUD를 관리하는 REST API기 때문에 명시적으로 @RestController라 작성해 주는 것이 좋은지 궁금합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RestController를 사용하고 있더라도 리턴 타입은 ResponseEntity로 해야합니다. ResponseEntity 타입은 HttpEntity를 상속하고 있는 클래스라서, 직접 결과 데이터와 HTTP 상태코드를 컨트롤할 수 있는 클래스입니다. 때문에 헤더, 상태코드, 결과값을 모두 제어하여 응답할 수 있고 에러 코드를 섬세하게 설정할 수 있습니다.

시간 날 때 @RestController에서 ResponseEntity를 사용한 api와 아닌 것을 비교해보세요! 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변 감사합니다!
그렇다면 CRUD api를 관리하는 컨트롤러의 경우 @Controller보다는 @RestController를 사용하는 것이 더 적절한 것이 맞을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

�좀 더 정확히는 restful한 api를 사용하려면 @RestController를 사용하는 것이 적절하겠네요!


import java.util.Objects;

public class Item {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 미션에서는 Entity와 Domain을 동일하게 바라보고 진행했습니다.
이에 대한 춘식의 의견이 궁금합니다!

처음에 Dao의 데이터를 전달 및 반환하기 위해 Entity를 생성했습니다.
그런데 도메인 로직이 검증 외에도 있다는 가정하에 Entity와 Domain을 분리해야 하는 가?라는 생각을 하게 되었습니다.
물론 id가 있어 도메인으로써 불필요한 데이터가 있긴 하지만, 이번 미션에서는 굳이 Entity와 Domain을 분리할 필요 없다 판단하여 두 가지의 역할을 모두 수행하도록 해보았습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 엔티티와 도메인을 강력하게 분리할 필욘 없습니다. 엔티티는 핵심 비즈니스 로직을 담는 비즈니스 도메인 영역의 일부이기 때문입니다. 때문에 도메인과 마찬가지로 엔티티에도 비즈니스 로직이 추가될 수 있습니다. 🙂

엔티티를 다룰 때는 DTO처럼 계층 간 데이터 전달을 위한 객체로만 사용하게 되는 것만 조심하면 됩니다.

Copy link
Member Author

@JJ503 JJ503 May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변 감사합니다!
그러면 이번 미션에서는 도메인과 엔티티를 동일하게 바라보고 진행해 보도록 하겠습니다!
아직 도메인의 역할이 검증 외에는 없기에 크게 장단점이 잘 느껴지지 않지만, 2단계 미션을 진행하면서 도메인과 엔티티를 동일하게 바라봤을 때의 장단점을 좀 더 고민해 보도록 하겠습니다!

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;

public class ItemRequest {
Copy link
Member Author

@JJ503 JJ503 Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 Controller에서 Dto를 통해 값을 검증하는 방법과 도메인에서 vo를 통해 검증하는 방법을 모두 사용하고 있습니다.

우선 도메인의 경우 무결해야 하므로 검증이 필요하다고 생각해 도메인에서도 검증을 진행했습니다. 이는 해당 도메인에는 잘못된 값이 들어가서는 안 되기 때문입니다.
또한, DTO의 경우 조건에 맞는 데이터를 전달해 주기 위함이라 생각합니다.
그런데 dto를 통해 값 검증이 된다 하더라도, 혹시 모를 상황을 위해 중요한 조건 및 이외의 조건들은 도메인에서 처리해주어야 한다고 생각합니다.

이번 미션에서는 도메인과 컨트롤러에서 검증하는 것이 모두 동일하게 진행했습니다.
이때, 동일한 검증을 도메인과 컨트롤러 모두에서 수행하는 것에 대해 어떻게 생각하시는지 궁금합니다.
또한, 도메인에서 검증해야 한다고 생각할 때 춘식의 경우 중요한 검증에 대한 기준을 어떻게 세우는지 궁금합니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증은 다다익선이기 때문에 이상적으로는 도메인과 dto에서 모두 검증하는 게 좋습니다ㅎㅎ 하지만 그럴 여력이 없으면 dto에서 최대한 빡세게 잡고 도메인은 모든 값이 잘 들어왔다는 가정 하에 로직을 수행하기도 합니다. 🥲

도메인에서 검증해야할 중요한 로직이라면 가공된 값 또는 도메인 요구사항을 검증할 때 사용하는게 좋다고 봅니다. 지갑에서 돈이 입출금 되는 api를 예시로 들어보겠습니다. 입출금되는 값이 음수나 문자인지는 dto에서 검증할 수 있습니다. 그러나 한번에 지갑에 있는 돈의 20% 이상을 출금할 순 없다는 식의 도메인 요구사항은 dto로 검증할 수 없습니다. 이런 경우에는 도메인 단에서 검증하도록 하면 됩니다. 😎

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여력이 없을 때는 도메인에 해야 하지 않을까 싶었는데 일반적으로는 dto에서 수행하는군요…!
dto가 아닌 도메인에서만 검증을 수행했을 때의 단점이 존재하는 것일까요??

또한, 현재 미션과 같은 경우는 시간적으로는 문제가 되지 않기에 도메인과 dto 모두에서 검증할 여력이 되는 상황입니다.
이 경우 모두에서 검증하는 것이 좋을까요? 혹은 dto와 도메인 각각에서 검증해야 할 사항에 대해 기준을 세워보는 연습을 하는 것이 좋을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dto가 아닌 도메인에서만 검증을 수행했을 때의 단점이 존재하는 것일까요??

단점이 따로 있는 건 아니고 개인 취향으로 보면 됩니다. 개인적으로 API를 많이 짜다보면 값의 타입과 범위를 검증하는 작업이 반복적이고 특징적인 것도 아니라 깐깐하게 짤 중요성을 크게 느끼지 못하게 되거든요 🥲 하지만 앞서 말했듯이 양쪽에서 모두 잡는게 이상적이긴 합니다ㅎㅎ

제이미가 이번 미션을 어떻게 다루고 싶은지에 따라 다를 것 같습니다. 꼼꼼한 코드를 짜고 싶다면 전자로 진행하고 기준을 고민하면서 본인만의 코드 철학을 쌓아보고 싶다면 후자로 진행하면 될 것 같네요 👍

Copy link

@bimppap bimppap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 제이미, 춘식입니다!
못 본 사이에 실력이 많이 늘어난 게 보이네요! 👍👍레벨 2도 잘 부탁드립니다ㅎㅎ
질문에 대한 답과 코멘트들 조금 남겨봤으니 확인 부탁드립니다.
궁금하거나 이해가 안 되는 부분은 편하게 DM 주세요.

import java.util.List;

@Controller
public class ListController {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List는 자바에서 쓰이는 자료형과 이름이 같아 의미 전달이 잘 안 되는 것 같습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdminController ListController를 분리한 이유가 있을까요? 둘 다 같은 서비스를 사용하고 있고, resource가 아닌 view를 담당하는 컨트롤러들이라 하나로 합쳐도 무방할 것 같습니다

Copy link
Member Author

@JJ503 JJ503 May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List는 자바에서 쓰이는 자료형과 이름이 같아 의미 전달이 잘 안 되는 것 같습니다!

변수명 등에서 자료형을 사용하지 말아야 함을 알고 있었는데, 의미 전달만 생각하다 보니 이를 간과했네요..!


AdminController ListController를 분리한 이유가 있을까요? 둘 다 같은 서비스를 사용하고 있고, resource가 아닌 view를 담당하는 컨트롤러들이라 하나로 합쳐도 무방할 것 같습니다

ListController의 경우 메인 페이지를, AdminController는 관리자 페이지를 담당한다는 생각 해 자연스럽게 컨트롤러가 분리된 것 같습니다.
동일한 서비스이며, 모두 view를 반환하므로 하나로 합쳐도 무방하군요!
ItemViewController로 수정해 첫 번째와 두 번째 의견에 대해 해결해 보았습니다.


추가 의문점
그렇다면, 기능들이 추가되어 여러 페이지가 생겼을 때 동일 서비스를 사용하고 view를 반환한다면 모두 한 컨트롤러에 모아두어도 크게 상관이 없을까요?
혹은 페이지가 너무 많은 경우 비슷한 url 혹은 기능끼리 묶어 컨트롤러를 분리시키는 것이 좋을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페이지를 기준으로 컨트롤러를 분리시키는 점에 대해선 깊게 고민하지 않아도 될 것 같습니다. 🤔 보통 실무에서는 view 관련 처리는 프론트에서 전부 하고 있어서 백엔드가 view를 만질 일이 거의 없거든요. (작은 서비스의 관리자 페이지는 백엔드가 프론트까지 도맡아서 진행할 때도 있어서 view를 만질 일이 아예 없는 건 아닙니다.)

그래도 답을 해보자면 view가 적다면 한 곳에 모아두고 아니라면 도메인 단위로 두는 게 좋지 않을까 싶네요. 제이미 생각과 다르다면 같이 얘기를 나눠봐도 좋을 것 같아요. 🙂

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

// TODO: 2023/04/26 text/html을 기대할 떄와, application/json을 기대할 떄 똑같이 Json으로 예외 결과를 응답하는게 맞을까?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 해결된 걸까요?ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

완벽히 해결되지는 않은 상태입니다…!
다만, test/html을 기대하는 경우 현재 상태에서는 모두 application/json으로 예외를 전달하기에 예외가 발생한 경우 아래와 같이 예외 결과가 보이게 됩니다.

image

현재 예외 페이지가 따로 존재하는 것이 아니긴 하지만 이는 클라이언트 측에서 원하는 예외 발생의 응답은 아니라고 생각하기에 test/html로 반환하는 ExceptionControllerAdvice가 필요하지 않을까 생각합니다.
이에 대한 춘식의 의견도 알려주시면 감사하겠습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 500 에러일 때 text/html을 기대하게 된다는 뜻일까요? 어떤 경우에 text/html을 기대하고 있는 건지 이해가 잘 되지 않는데 좀 더 자세히 설명해줄 수 있을까요? 🥲

(+) json formatter 를 사용하면 가독성이 훨씬 좋아집니다!

Copy link
Member Author

@JJ503 JJ503 May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

500 에러일 때 text/html을 기대한다기보다는 예외를 발생시키는 url로 접근했을 때 예외 페이지가 아닌 json의 형태로 예외 메시지가 보여도 괜찮은지에 대한 의문이었습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 그런 케이스라면 json 형태의 메세지가 보여도 상관없습니다. 프론트에서 예외 페이지를 만들어준다면 더 좋겠지만요ㅎㅎ

log.info(exception.getMessage());
HttpStatus badRequest = HttpStatus.BAD_REQUEST;
ExceptionResponse exceptionResponse = new ExceptionResponse(badRequest.value(), badRequest.getReasonPhrase(), exception.getMessage());
return ResponseEntity.status(badRequest)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return ResponseEntity.status(badRequest)
return ResponseEntity.badRequest()

이러면 위에서 HttpStatus 변수를 안 만들어도 되겠네요 😎

Copy link
Member Author

@JJ503 JJ503 May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status()메서드를 통해 상태 코드를 설정하지 않아도 됐었군요!
좋은 팁 감사합니다!

Comment on lines 50 to 53
@ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionResponse> handlerException(Exception exception) {
log.info(exception.getMessage());
HttpStatus internalServerError = HttpStatus.INTERNAL_SERVER_ERROR;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예상치 못한 에러가 났을 때도 로그를 info 레벨로 남기는 게 맞을까요? 로그의 각 레벨이 무얼 나타내기 위함인지 한번 정리해보면 좋을 것 같습니다🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 레벨 로그들을 조사해 정리해 본 결과 예외 처리 시 사용해야 하는 로그는 warn과 error로 변경해야 한다는 생각이 들었습니다.
info의 경우 정상 작동에 대한 로그이므로 사용하지 않았습니다.
이때, 사용자 입력에 대한 예외, DB 정보 불러오기에 대한 예외, 도메인 검증 등에 대한 예외의 경우 언제든 발생할 수 있는 일반적인 문제 상황이라 판단하여 warn으로 변경했습니다.
그리고 예상치 못한 에러의 경우 개발자가 제대로 조치해두지 못한 문제일 수도 있기에 error 레벨을 통해 심각한 오류 혹은 예외 상황이라 알리도록 변경했습니다.

위와 같은 이유로 각각 warn 레벨, error 레벨로 수정했는데 이에 대한 춘식의 의견이 궁금합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 알아보셨네요! 👍

로깅 레벨은 팀바이팀이지만 제 경험에 기반해 얘기해보자면, 클라이언트가 에러를 내는 건 개발자들의 예상 범위 안이라 정상 작동으로 봐도 무방합니다. 또한 따로 대응할 필요가 없기 때문에 info로 둡니다. 하지만 예상치 못한 에러는 제이미 말대로 치명적일 수 있어 error 레벨로 두는 편입니다. warn은 결제와 같은 중요 서비스에서 사용했던 것 같네요. 🤓

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

춘식의 의견에 따라 클라이언트에서 잘못 입력해 @Valid에서 오류가 발생하는 경우는 info로 도메인 검증 혹은 DB에 데이터가 없는 등의 문제는 warn, 이 외 예상하지 못한 문제의 경우는 error로 레벨링 해보았습니다!

private final ImageUrl imageUrl;
private final Price price;

public static class Builder {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빌더를 사용한 이유가 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id와 image url의 경우 꼭 입력하지 않아도 되는 정보로, 즉 null이 되어도 괜찮은 데이터입니다.
이때, 빌더를 사용하게 된다면 필요한 데이터만 설정할 수 있으므로 이에서 오는 장점이 있다고 생각했습니다.
이에 따라 어떤 데이터의 값을 설정했는지에 대한 가독성도 높아지리라 생각해 사용하게 되었습니다.

이에 대해 춘식은 어떻게 생각하시나요?

Copy link

@bimppap bimppap May 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빌더 패턴은 확실히 장점이 많고 저도 좋아하는 패턴입니다! 보통 빌더 패턴은 코드가 굉장히 길어져서 롬복을 사용하거나 생성자를 사용하는데 직접 쓰는 건 오랜만에 봐서 한번 물어봤습니다ㅋㅋ 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 롬복을 잘 몰라 빌더 패턴을 사용하게 되었네요!
그러면 추후 롬복을 배우게 된다면 빌더 패턴 대신 롬복을 사용하는 것이 더 좋을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

롬복에서 빌더 어노테이션을 사용하고 있기에 코드를 간략하게 쓰고 싶다면 롬복을 사용하는게 좋습니다. 치트키(ㅋㅋ)같다고 사용하지 않는 경우도 있긴 해요. 롬복은 팀마다 사용 여부를 달리하고 있지만 개인적으로 지금은 공부하는 단계라 롬복을 사용하는 건 나중으로 미루면 좋을 것 같네요ㅎㅎ


@DisplayName("POST /items 요청 시 addItem 메서드가 호출된다")
@Test
void addItemMappingURL() throws Exception {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저장, 수정 api를 테스트할 땐 적절한 값이 저장/수정되었는지 dao 또는 get api로 검증하는 코드도 넣어주는 것이 좋습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 검증 방법에 대해 몇 가지 궁금한 점이 있습니다!

  1. 컨트롤러 테스트의 경우 service부터 mock으로 만들기 때문에 dao를 사용한 검증을 어떻게 할 수 있을지 궁금합니다. mock을 만들지 않는 것이 좋은 것일까요?
  2. 말씀해 주신 get api를 통한 검증은 GET /items와 같은 api를 호출해 검증한다는 의미가 맞을까요?
  3. dao 단위 테스트인 ItemDaoTest에서 저장 및 수정을 수행했을 때 값이 제대로 DB에 저장되었는지 검증하는 로직이 있는 상태입니다. 이로는 부족할까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. dao도 mock 하여 사용할 수 있지만 컨트롤러 테스트에서 dao를 써도 되냐에 대해선 개발자 내에서 의견이 갈리는 부분이기 때문에 취향에 맞게 사용하면 됩니다!
  2. 제이미가 이해한 방식이 맞습니다. 🙂
  3. Dao 테스트는 Dao 자체가 잘 돌아가는지 확인하는 테스트지만 통합 테스트는 어떤 요청에 대해 애플리케이션의 모든 구성 요소가 예상대로 함께 작동하는지 확인하는 테스트입니다.

Copy link
Member Author

@JJ503 JJ503 May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변에 대해 추가적인 질문이 있어 여쭤봅니다!

  1. POST /items를 테스트하는 로직에 GET /items를 사용해 검증하게 된다면 GET /items에 문제가 생겼을 때 POST /items를 테스트하는 로직에서도 문제가 발생하게 됩니다. 이러한 문제점이 염려되어 진행하지 않게 되었는데 위와 같은 부분이 괜찮은 것인지 궁금합니다!
    다른 테스트에서도 이러한 부분을 해결하기 위해 제가 작성한 메서드를 이용하지 않고 JdbcTemplate을 사용해 직접 쿼리를 날려 확인한 상태입니다.

  2. ItemController의 경우는 통합 테스트가 아닌 컨트롤러에 대한 단위 테스트로 작성한 테스트입니다! 이와 같은 경우에도 확인이 필요한지 궁금합니다. 현재 통합 테스트는 JwpCartApplicationTests입니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. post와 get을 각각 테스트할 때는 문제가 없는데 연계를 할 때 문제가 생긴다면 완벽한 테스트라고 볼 수 있을까요? 테스트는 이 api가 안정적으로 기능을 수행하는지 검증하는 작업이기에 최대한 철저하게 가는게 좋습니다. 하지만 post후 데이터를 확인하는 이유는 적절한 값이 잘 들어갔는지를 보는거라 쿼리로 수행해도 무방합니다.
  2. 흠 통합 테스트는 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트이기 때문에 컨트롤러는 단위보단 통합 테스트라고 봐야 합니다. JwpCartApplicationTests는 인수 테스트라고 봐야 하고요. 단위, 통합, 인수 테스트에 대해 정리해보는 게 좋을 것 같습니다!

(
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
image_url VARCHAR(5000),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

varchar대신 text를 써보는 건 어떨까요? 둘이 무슨 차이점이 있는지도 정리해볼까요? 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 타입 모두 크기가 가변적이긴 하지만, text가 주로 대용량 데이터를 저장하는 데 사용함을 알게 되었습니다.
그런데 두 타입 모두 최대 크기는 65535로 동일한 것으로 보이는데 대용량의 데이터의 경우 text를 사용해야 정확한 이유에 대해서는 아직 제대로 이해하지 못했습니다… 😥

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

varchar는 길이를 지정할 수 있고 text는 무조건 최대값이 65535로 고정됩니다. 그래서 길이의 제한이 중요하지 않거나 최대 길이가 불확실한 대용량 데이터를 저장할 땐 text를 쓰게 됩니다.

예시로 한국이름은 최소 1자 최대 6자입니다. (법적으로 정해져있습니다!) 이런 경우엔 varchar(6)을 사용하는 게 좋습니다. 반면 댓글의 경우, 어떤 게시물은 아예 안 달릴 수도 있습니다. 어떤 게시물은 몇 천, 몇 만개가 달릴 수도 있습니다. (댓글 수 제한이 있는 게시물을 본 적도 없고요!) 이 경우엔 varchar로 길이를 지정했다가 오류가 날 수도 있으니 text를 쓰는게 유리합니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변 감사합니다!
확실히 varchar(65525)처럼 최댓값을 지정하는 것보단 text를 활요하는 것이 더 편하겠네요!

@@ -60,20 +65,26 @@ const updateProduct = (product) => {
const { id } = product;

axios.request({
url: '',
url: '/items/' + id,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

완료된 todo 주석은 얼른 지워줍시다ㅋㅋ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깜빡했네요! 주석 제거해 주었습니다.
감사합니다!

}
}

public Long save(final Item item) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batchUpdate에 대해서 알아보면 좋을 것 같습니다. 이건 권장사항이라 꼭 반영할 필욘 없습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batchUpdate를 통해 여러 정보를 한 번에 저장하는 등 여러 쿼리를 한 번에 수행할 수 있군요!
그런데 이번 미션에서는 한 번에 여러 데이터를 저장하는 등 여러 쿼리를 수행해야 하는 경우가 보이지 않아 이후 필요하다면 사용해 보도록 하겠습니다!
좋은 키워드 감사합니다!

batchUpdate를 고민하다 추가적으로 든 의문사항이 있습니다.
수정, 삭제 등을 하기 전에 id가 존재하는지에 대한 검증 방법으로 생각나는 것은 다음 두 가지 방법입니다.

  1. findBy() 메서드를 통해 해당 행이 존재하는지 확인 후 수정/삭제 진행 (현재 사용)
  2. 수정/삭제를 진행한 후 update 된 행의 개수를 통한 검증

batchUpdate는 여러 쿼리 수행을 한 번해 수행해 여러 번 DB에 요청을 수를 줄일 수 있습니다.
이와 마찬가지로 findBy() 메서드보다는 수정/삭제된 행 개수를 통해 해당 id가 있는지 검증하는 것이 더 좋을지 춘식의 의견이 궁금합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증 방법은 개인 취향에 맡기면 될 것 같습니다 🙂 개인적으로 저는 전자는 예방의 느낌이고 후자는 저지르고(?) 확인하는 느낌이라 전자를 선호합니다ㅎㅎ

Copy link
Member Author

@JJ503 JJ503 May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 현재는 findBy()를 통해 해당 데이터가 있는지 확인한 후 이벤트를 수행하는 순서가 좀 더 적절하다고 생각되어 원래 상태를 유지했습니다!

Comment on lines +64 to +66
final String sql = "UPDATE items SET name = ?, image_url = ?, price = ? WHERE id = ?";
jdbcTemplate.update(sql, item.getName(), item.getImageUrl(), item.getPrice(), item.getId());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedParameterJdbcTemplate를 사용해봐도 좋을 것 같네요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고민해 본 결과 NamedParameterJdbcTemplate은 객체의 필드와의 매핑을 자동으로 수행해 주는 것을 확인했습니다.
getter 외에는 builder 및 원시값 포장으로 인해 NamedParameterJdbcTemplate을 사용하는 것이 어려운 것 같습니다.
그런데 getter 사용만을 위해 NamedParameterJdbcTemplate도 함께 선언해 주는 것이 좋은지 궁금합니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedParameterJdbcTemplate를 꼭 적용할 필욘 없습니다! jdbcTemplate의 확장 버전인 만큼 유용하고 편리하게 사용할 수 있는 기능들이 있는거지 jdbcTemplate와 비교했을 때 엄청난 성능 차이가 나는 건 아니라서요.🙄

그런데 어떤 점에서 어려움을 겪고 있는 걸까요? BeanPropertySqlParameterSource이나 MapSqlParameterSource를 사용하면 적용이 아주 어렵진 않을텐데 제가 놓친 부분이 있는 건가 싶네요ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

죄송합니다...!
NamedParameterJdbcTemplate을 적용하려고 했을 때 왜인지 RowMapper가 아닌 다른 방식으로 매핑이 된다고 생각했습니다.
2단계 미션을 수행하면서 NamedParameterJdbcTemplate를 공부해 보며 적용해 보았습니다!

@JJ503
Copy link
Member Author

JJ503 commented May 1, 2023

안녕하세요 춘식!
좋은 의견들과 피드백들 감사합니다.
해당 내용을 바탕으로 고민해 보며 피드백을 적용해 보았습니다.

의견과 피드백에서 대한 추가 질문에 대해 코멘트로 달아두었습니다.
추가적으로 제가 좀 더 고민해 봐야 하거나, 잘못 이해하고 있는 부분이 있다면 말씀해 주시면 감사하겠습니다!

Copy link

@bimppap bimppap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 제이미, 춘식입니다!
리팩토링 잘 해주셨어요 👍 코드를 고치면서 계속 고민해보는 자세에 감탄하고 갑니다ㅎㅎ
이번 단계는 여기서 마무리해도 될 것 같아 머지하도록 하겠습니다.
질문에 대한 답들 남겨놓았으니 확인하고 2단계 진행해주세요.
질문의 질문(?)은 여기서 답해도 되고 2단계 코멘트로 남겨놓아도 됩니다.
궁금하거나 이해가 안되는 부분이 있으면 편하게 DM 주세요. 다음 단계에서 봐요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants