-
Notifications
You must be signed in to change notification settings - Fork 177
[2단계 - 장바구니 기능] 히이로(문제웅) 미션 제출합니다 #329
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
세부사항 - cart 정보 응답 시 id값을 item이 아닌 product 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.
안녕하세요 히이로
전체적으로 엄청 깔끔하게 짜주신것 같아요!👍
이번 리뷰에서는 인증과 관련된 부분 위주로 남겼습니다
MethodParameter parameter, | ||
ModelAndViewContainer mavContainer, | ||
NativeWebRequest webRequest, | ||
WebDataBinderFactory binderFactory) throws Exception { |
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.
throws Exception은 없어도 되겠네요
} | ||
|
||
@Override | ||
public Object resolveArgument( |
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 Object resolveArgument( | |
public AuthInfo resolveArgument( |
AuthInfo로 바꿔도 되지 않나요?
체크해봐주세요!
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.
밑에서 언급해주신 throws Exception 부분도 그렇고 인터페이스를 구현하는 과정에서 메서드 시그니처를 반드시 그대로 따를 필요는 없다는 것은 몰랐네요... 아직 스프링 프레임워크의 전체적인 구조를 머리속에 그리지 못하다보니 이런 세부적인 것들을 건드리기가 무섭더라고요... ㅎㅎ 감사합니다!
private final Long memberId; | ||
private final Long productId; |
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.
여기도 도메인에서는 Member, Product를 필드로 가지는게 적절하지 않을지 고민해봐주세요!
); | ||
public ItemResponse createItem(Long memberId, Long productId) { | ||
Cart memberCart = getCartByMemberId(memberId); | ||
Item requestedItem = new Item(memberId, productId); |
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와 member 를 기반으로 item이 만들어지는것 같은데
실제 product와 member 값이 아닌
id를 통해서만 로직이 처리되고 있는것 같네요
밑에서 productDao를 통해 실제 product를 조회하고 있긴한데 response dto를 만들기위해 조회를 하고 있어요
로직 흐름상 먼저 조회를 해서 완벽한 product 로 처리가 되어야하지 않을까요?
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 설계에 도메인 설계가 영향을 많이 받아서 이런 결과를 낳게 되었습니다.
도메인의 다른 부분에서 리뷰해주신 부분들도 이 때문에 발생했던 문제라고 생각됩니다.
해당 부분들은 영속성 로직의 측면이 아니라 도메인 설계적 측면에서 가져야 하는 프로퍼티들을 가질 수 있게끔 일괄적으로 수정했습니다. 또한 BusinessLayer(Service)에서 최대한 도메인이 먼저 완성된 이후에 영속성 로직 혹은 비즈니스 로직이 수행될 수 있는 방향으로 수정했습니다.
앞으로는 반드시 도메인 설계를 최우선적으로 수행한 이후에 영속성 로직과 API 설계를 고려하는 순으로 개발을 진행해야 한다는 것을 배울 수 있었습니다! 😀
created.getPrice(), | ||
created.getImageUrl()); | ||
Long registeredItemId = cartDao.createItem(requestedItem.getMemberId(), requestedItem.getProductId()); | ||
Item registeredItem = cartDao.findItemById(registeredItemId); |
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 설계시 step1에서 피드백 주신대로 생성 요청 시 생성된 항목을 반환하는 방식을 고수하고 있습니다. 이 부분을 조회를 한 번 더 하지 말고 이미 메서드에 있는 정보들을 바탕으로 반환 값을 만들어서 반환해주라는 의미로 이해하고 리팩토링을 진행했습니다.
혹시 제가 생각한 방향이 틀렸다면 알려주시면 감사하겠습니다!
return new AuthInfo(email, password); | ||
} | ||
|
||
return 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.
@AuthResolving이 붙은 곳에서 null이 반환되고 문제없이 진행되기보다(물론 controller에서 체크하지만)
여기서 throw를 하는게 더 안전하지 않나요?
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에 request 메세지가 도달하기 전에 LoginInterceptor가 Authorization 헤더에 대해 null인지 한 번 검증하고 있습니다. 그래서 현재 BasicAuthorizationExtractor 클래스에서 헤더가 null인지 검증하는 부분에서는 LoginInterceptor에서 throw되는 것과 동일한 예외를 던지도록 수정했습니다.
그리고 리뷰해주신 마지막에 null을 반환하는 부분에 대해서는 Authorization 헤더가 정상적으로 존재하나 Basic 방식을 채택하지 않은 값이 들어있을때 수행되게 되므로 새로운 customException을 정의하여 반환하도록 수정하였습니다!
public class LoginInterceptor implements HandlerInterceptor { | ||
|
||
@Override | ||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
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.
memberService를 사용하는게 불가능하다고 하셨는데 이유가 뭘까요?😭
interceptor는 spring의 영역이라서 충분히 가능할것 같은데요
단순히 member조회라면 memberDao만을 사용하는것도 방법이라고 생각합니다
혹시 인터셉터를 @bean 어노테이션으로 직접 빈으로 등록하여 꼬여서 안되는거라면 @component를 사용해보면 될것 같습니다!
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.
앗 제가 질문을 드릴 때 어떻게 해서 불가능하다고 판단했는지를 말씀을 안드렸네요!
처음 LoginInterceptor를 @component로 선언하고 memberService를 @Autowired를 사용해 의존성을 주입하려고 했습니다. 그런데 @configuration 클래스에서 LoginInterceptor를 등록할 때 아래와 같이 Interceptor를 생성하면서 등록을 해주고 있었습니다.
// CartWebConfiguration...
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/api/cart/**");
}
그런데 LoginInterceptor에서 memberService를 생성자 주입을 통해 사용하게 하기 위해 생성자를 열어주면 다음과 같은 코드로 바뀌었습니다.
// CartWebConfiguration...
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor(membersService); // membersService를 configuration 클래스에서 주입...?
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/api/cart/**");
}
여기에서 Configuration 클래스에서 memberService를 LoginInterceptor에게 직접 주입하는 방식에 대한 확신이 들지 않았기에 불가능하다고 판단했습니다. 하지만 리뷰를 받고 다시 알아보니 @configuration 클래스도 @component로 관리되기에 빈을 주입받을 수 있어서 memberService를 주입해서 등록해주는 방식으로 수정했습니다!
throw new MissingAuthorizationHeaderException(); | ||
} | ||
|
||
return true; |
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.
그리고 인증과 관련된 작업들은 interceptor에서 처리하는게 적절하지 않을까요?
argumentResolver는 원하는 객체로 만들어서 반환하는 역할만 하는게 적절해보입니다!
interceptor에서 인증과 관련된 로직을 처리하고 request.setAttribute() 를 활용해 arugmentResolover에서 email, password를 꺼내서 사용만 하는 형태가 되면 어떨지 고민해봐주세요!
그렇게 된다면 extractor는 없어지고 extractor의 로직 대부분이 interceptor에서 처리될듯하네요!
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.
동의합니다. 미션을 진행하면서 인증관련된 로직은 LoginInterceptor가 모두 처리하도록 하고 싶었는데 위에서 언급했던 것과 같이 membersService 의존성 문제를 해결하지 못했기에 이렇게 구현하게 되었습니다. 의존성 문제를 해결하고 LoginInterceptor에서 인증과 setAttribute를 통해 email과 password를 request 메세지의 속성으로 추가할 수 있도록 했습니다.
덕분에 인자로 넘어오는 request와 response 객체에 대해 필요에 따라 적절하게 작업을 수행해 줄 수 있다는 점을 배울 수 있었습니다. 감사합니다! 😄
|
||
public class Cart { | ||
|
||
private final Long memberId; |
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.
memberId가 아닌 실제 Member를 가지고 있어야하지 않을까요?
@@ -0,0 +1,20 @@ | |||
package cart.dto.auth; | |||
|
|||
public class AuthInfo { |
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.
interceptor에서 인증이 제대로된다면
argument resolver에서 반환되는 객체는 Member가 되고
AuthInfo는 필요없겠네요!
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.
영이가 리뷰해주신대로 따라가다 보니 다음과 같이 처음에 생각했던 인증 절차대로 구현할 수 있었습니다.
-
MembersService 기능을 사용해서 LoginInterceptor가 인증 절차를 진행하고 request 메세지의 attribute로 email과 password를 추가합니다.
-
이후 MembersDao 기능을 사용해서 ArgumentResolver가 request메세지의 email 속성 값을 받아와 member 객체를 완성해 반환합니다.
다만 AuthInfo dto는 loginInterceptor에서 membersService로 데이터를 넘겨 인증 과정을 처리할 때 필요하므로 삭제하지는 않았습니다. 😄
세부사항 - 어노테이션 명 변경 - BasicAuthorizationExtractor에서 null 대신 예외를 throw하도록 수정
세부사항 - 기존 BasicAuthorizationExtractor 기능 LoginInterceptor로 이관 - AuthArgumentResolver는 request 메세지의 attribute를 추출해서 넘겨주도록 수정
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.
안녕하세요 영이, 히이로입니다!
인증관련 부분과 도메인 부분에서 막연하게 느꼈던 부분들을 잘 짚어주셔서 크게 엇나가지 않고 원하는 방향으로 리팩토링할 수 있었던 것 같습니다. 혹시 제가 잘못된 방향으로 이해하고 수정한 부분이 있다면 말씀해주시면 감사하겠습니다!
영이께서 조목조목 리뷰로 잘 짚어주셨어서 이번 미션을 계기로 스프링 프레임워크에 대한 이해도나 관점을 많이 확장시킬 수 있었던 것 같습니다. 이번 미션동안 리뷰해주시느라 정말 감사했습니다! 🙇
} | ||
|
||
@Override | ||
public Object resolveArgument( |
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.
밑에서 언급해주신 throws Exception 부분도 그렇고 인터페이스를 구현하는 과정에서 메서드 시그니처를 반드시 그대로 따를 필요는 없다는 것은 몰랐네요... 아직 스프링 프레임워크의 전체적인 구조를 머리속에 그리지 못하다보니 이런 세부적인 것들을 건드리기가 무섭더라고요... ㅎㅎ 감사합니다!
public static final int CREDENTIALS_PASSWORD_INDEX = 1; | ||
|
||
public AuthInfo extract(HttpServletRequest request) { | ||
String header = request.getHeader(AUTHORIZATION); |
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와 관련된 상수들은 기본 스프링 프레임워크 패키지에서 제공을 한다는 것을 처음 알았습니다. 감사합니다. 😄
그런데 알려주신 Basic 상수 값은 찾아보니 스프링 시큐리티 패키지에 선언되어 있는 것 말고는 없다고 나오더라구요... 상수 값을 사용하기 위해 패키지를 import 하는 것은 뭔가 배보다 배꼽이 커지는 것 같아 일단 Basic은 상수로 유지하는 것으로 결정했습니다!
return new AuthInfo(email, password); | ||
} | ||
|
||
return 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.
현재 프로젝트에서는 controller에 request 메세지가 도달하기 전에 LoginInterceptor가 Authorization 헤더에 대해 null인지 한 번 검증하고 있습니다. 그래서 현재 BasicAuthorizationExtractor 클래스에서 헤더가 null인지 검증하는 부분에서는 LoginInterceptor에서 throw되는 것과 동일한 예외를 던지도록 수정했습니다.
그리고 리뷰해주신 마지막에 null을 반환하는 부분에 대해서는 Authorization 헤더가 정상적으로 존재하나 Basic 방식을 채택하지 않은 값이 들어있을때 수행되게 되므로 새로운 customException을 정의하여 반환하도록 수정하였습니다!
public class LoginInterceptor implements HandlerInterceptor { | ||
|
||
@Override | ||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
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.
앗 제가 질문을 드릴 때 어떻게 해서 불가능하다고 판단했는지를 말씀을 안드렸네요!
처음 LoginInterceptor를 @component로 선언하고 memberService를 @Autowired를 사용해 의존성을 주입하려고 했습니다. 그런데 @configuration 클래스에서 LoginInterceptor를 등록할 때 아래와 같이 Interceptor를 생성하면서 등록을 해주고 있었습니다.
// CartWebConfiguration...
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/api/cart/**");
}
그런데 LoginInterceptor에서 memberService를 생성자 주입을 통해 사용하게 하기 위해 생성자를 열어주면 다음과 같은 코드로 바뀌었습니다.
// CartWebConfiguration...
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor(membersService); // membersService를 configuration 클래스에서 주입...?
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/api/cart/**");
}
여기에서 Configuration 클래스에서 memberService를 LoginInterceptor에게 직접 주입하는 방식에 대한 확신이 들지 않았기에 불가능하다고 판단했습니다. 하지만 리뷰를 받고 다시 알아보니 @configuration 클래스도 @component로 관리되기에 빈을 주입받을 수 있어서 memberService를 주입해서 등록해주는 방식으로 수정했습니다!
throw new MissingAuthorizationHeaderException(); | ||
} | ||
|
||
return true; |
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.
동의합니다. 미션을 진행하면서 인증관련된 로직은 LoginInterceptor가 모두 처리하도록 하고 싶었는데 위에서 언급했던 것과 같이 membersService 의존성 문제를 해결하지 못했기에 이렇게 구현하게 되었습니다. 의존성 문제를 해결하고 LoginInterceptor에서 인증과 setAttribute를 통해 email과 password를 request 메세지의 속성으로 추가할 수 있도록 했습니다.
덕분에 인자로 넘어오는 request와 response 객체에 대해 필요에 따라 적절하게 작업을 수행해 줄 수 있다는 점을 배울 수 있었습니다. 감사합니다! 😄
); | ||
public ItemResponse createItem(Long memberId, Long productId) { | ||
Cart memberCart = getCartByMemberId(memberId); | ||
Item requestedItem = new Item(memberId, productId); |
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 설계에 도메인 설계가 영향을 많이 받아서 이런 결과를 낳게 되었습니다.
도메인의 다른 부분에서 리뷰해주신 부분들도 이 때문에 발생했던 문제라고 생각됩니다.
해당 부분들은 영속성 로직의 측면이 아니라 도메인 설계적 측면에서 가져야 하는 프로퍼티들을 가질 수 있게끔 일괄적으로 수정했습니다. 또한 BusinessLayer(Service)에서 최대한 도메인이 먼저 완성된 이후에 영속성 로직 혹은 비즈니스 로직이 수행될 수 있는 방향으로 수정했습니다.
앞으로는 반드시 도메인 설계를 최우선적으로 수행한 이후에 영속성 로직과 API 설계를 고려하는 순으로 개발을 진행해야 한다는 것을 배울 수 있었습니다! 😀
created.getPrice(), | ||
created.getImageUrl()); | ||
Long registeredItemId = cartDao.createItem(requestedItem.getMemberId(), requestedItem.getProductId()); | ||
Item registeredItem = cartDao.findItemById(registeredItemId); |
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 설계시 step1에서 피드백 주신대로 생성 요청 시 생성된 항목을 반환하는 방식을 고수하고 있습니다. 이 부분을 조회를 한 번 더 하지 말고 이미 메서드에 있는 정보들을 바탕으로 반환 값을 만들어서 반환해주라는 의미로 이해하고 리팩토링을 진행했습니다.
혹시 제가 생각한 방향이 틀렸다면 알려주시면 감사하겠습니다!
@@ -0,0 +1,20 @@ | |||
package cart.dto.auth; | |||
|
|||
public class AuthInfo { |
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.
영이가 리뷰해주신대로 따라가다 보니 다음과 같이 처음에 생각했던 인증 절차대로 구현할 수 있었습니다.
-
MembersService 기능을 사용해서 LoginInterceptor가 인증 절차를 진행하고 request 메세지의 attribute로 email과 password를 추가합니다.
-
이후 MembersDao 기능을 사용해서 ArgumentResolver가 request메세지의 email 속성 값을 받아와 member 객체를 완성해 반환합니다.
다만 AuthInfo dto는 loginInterceptor에서 membersService로 데이터를 넘겨 인증 과정을 처리할 때 필요하므로 삭제하지는 않았습니다. 😄
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.
안녕하세요 히이로
피드백 잘 반영해주셨네요👍
이번미션은 머지해도 될것같습니다!
몇가지 마이너한 코멘트 남겼는데 챙겨봐주세요
long memberId = membersDao.findIdByEmail(email); | ||
|
||
return membersDao.findById(memberId); |
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.
email을 통해 id를 찾는것보다 emai을 통해 member를 찾는게 어떤가요?
한번에 찾을 수 있는걸 두번에 나눠서 하고 있는것 같아서요!
필요하다면 인덱스, 유니크 제약조건에 대해 공부하고 인덱스를 적용해보면 좋을것 같습니다
} | ||
|
||
private void validateAuthorization(AuthInfo authInfo) { | ||
if (!membersService.isMemberCertified(authInfo)) { |
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.
authInfo 객체를 만들어 넘기기보다는 그냥 email, password를 넘겨서 사용해도 되지않을까요?
request.setAttribute("email", email); | ||
request.setAttribute("password", password); |
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.
"email", "password" 는 key값이고 argumentResolver에서도 공통적으로 쓰이는거라 public 상수로 관리되면 좋을것 같네요
@Auth Member member, | ||
@RequestParam("product-id") Long productId) { | ||
|
||
ItemResponse itemResponse = cartService.createItem(member.getId(), productId); |
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.
argumentResolver에서 완성된 member를 조회했는데 여기서 다시 memberId만 넘겨주고 있어요!
|
||
@GetMapping("/items") | ||
public ResponseEntity<CartResponse> readItemsByMember(@Auth Member member) { | ||
CartResponse cartResponse = cartService.readAllItemsByMemberId(member.getId()); |
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만 쓰여서 상관없긴한데
쓰임새를 생각해보면 argumentResolver에서 memberId를 넘기거나 email을 넘기고
dao를 통한 조회를 실제 로직에서 처리하는 방법도 있겠네요
this.items = new ArrayList<>(); | ||
|
||
if (items.size() > 0) { | ||
this.items.addAll(items); | ||
} |
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.
item이 없다면 에러가 날필요는 없나요?
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.
비즈니스 로직 상 사용자의 장바구니가 초기에는 아무 상품도 담고 있지 않을 수 있기에, 또 cart 도메인 객체는 이를 나타낼 수 있어야 하기에 해당 부분을 예외처리하지는 않았습니다! 😄
Product product = new Product( | ||
productRequest.getName(), | ||
productRequest.getPrice(), | ||
productRequest.getImageUrl() | ||
); |
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.
@MoonJeWoong
product에 더 많은 정보를 담게 된다면 생성자 파라미터가 늘어나서 힘들어 지므로 아래처럼 하는 방법도 있음
Product.of(productRquest)
public class CartHasDuplicatedItemsException extends RuntimeException { | ||
public CartHasDuplicatedItemsException() { | ||
super("[ERROR] 장바구니에는 동일 상품을 중복으로 담을 수 없습니다."); | ||
} |
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.
@MoonJeWoong 서비스가 복잡해져서, 에러케이스가 많아지면
custom Exception class가 아주 많이 늘어날 수 있음.
그런 경우에는 보통 에러케이스를 정리해서 에러코드로 변경하고,
에러코드와 에러메시지를 세트로 관리함 -> 보통 enum 클래스 사용.
custom Exception class는 해당 서비스에서 매니징 할수있는 제한된 갯수(1-2개?)만 생성
throw new CartWebException(CartWebErrorCode.DUPLICATED_ITEMS);
public enum CartWebErrorCode {
.....
DUPLICATED_ITEMS("장바구니에는 동일 상품을 중복으로 담을 수 없습니다.");
....
}
안녕하세요 영이, 히이로입니다.
step1 리뷰를 빠르게 해주셨는데 이것 저것 삽질하면서 고민하다보니 리뷰 요청까지 시간이 오래 걸렸습니다... 😭
이번 step2 리뷰도 잘 부탁드립니다!
이번 step2를 진행하며 궁금했던 부분은 다음과 같습니다.