Conversation
|
|
||
| public final UUID productId; | ||
|
|
||
| public final Price pricePerUnit; |
There was a problem hiding this comment.
단순히 price를 사용하는 경우 메뉴 하나의 가격인지, 메뉴 가격에 수량을 곱한 소계에 해당하는 값인지 혼동되어 명확하게 네이밍했습니다.
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class MenuDisplayPolicy { |
There was a problem hiding this comment.
이 때는 별도의 클래스로 분리했지만 실제로 Menu aggrigate만을 사용하므로 Menu 내부에 두어도 문제가 없을 것 같다는 생각을 했습니다. 별도의 클래스로 분리하는 것과 내부에 두는 것 서로 장단점이 있는 것 같습니다.
There was a problem hiding this comment.
현재 구현해주신 코드는 Menu Aggregate만을 사용하게 되지만, 가격 변경시 메뉴에 속한 상품의 합보다 적거나 같아야한다.와 같은 정책이 지켜지지 않을것 같아요.
메뉴가 가지고 있는 가격이 현시점에 상품 가격이라고 확신할수 없는 구조 입니다.
| private final List<MenuProduct> menuProducts; | ||
|
|
||
| private Boolean isVisible; | ||
| private Boolean displayed; |
There was a problem hiding this comment.
굳이 isVisible로 바꿔야만 하는 이유가 없어서 불필요한 데이터베이스 변경을 만들지 않기 위해 displayed를 그대로 사용하기로 했습니다.
| import kitchenpos.common.name.Name; | ||
| import kitchenpos.common.price.Price; | ||
| import kitchenpos.menu.tobe.domain.service.MenuDisplayPolicy; | ||
| import kitchenpos.menu.tobe.domain.vo.MenuGroup; |
There was a problem hiding this comment.
구현을 하면서 MenuGroup에 id가 필요하다는 것을 알게 되었고, 그렇게 되면 MenuGroup entity와 vo의 차이가 없어져 entity를 그대로 사용하기로 했습니다.
다만 추후에 MenuGroup의 이름을 변경할 수 있게 되거나 하는 요구사항이 생긴다면 MenuGroup entity는 더이상 불변이 아니게 될 것이므로 이 때는 다시 불변인 VO를 추가하게 될 수도 있을 것 같습니다.
| @Component | ||
| public static class MenuProductToMenuProductEntityConverter { | ||
|
|
||
| public MenuProductEntity convert(final MenuProduct menuProduct, final Menu menu) { |
There was a problem hiding this comment.
기존의 Converter 인터페이스로 커버가 안되는 케이스가 발생해버렸습니다.
| public interface MenuGroupRepository { | ||
|
|
||
| MenuGroup save(MenuGroup menuGroup); | ||
| MenuGroup save(final MenuGroup menuGroup); |
| this.nameFactory = nameFactory; | ||
| } | ||
|
|
||
| public Menu convert( |
There was a problem hiding this comment.
여기서도 기존의 Converter 인터페이스로 커버되지 않는 케이스가 발생했습니다.
| ); | ||
| } | ||
|
|
||
| public List<MenuProduct> convert(final Collection<MenuProductEntity> menuProductEntities) { |
There was a problem hiding this comment.
데이터베이스에서 조회하는 경우 Collection에 포함된 엔티티를 모두 변환해야 할 필요가 있어 추가했습니다.
| final List<MenuProductEntity> menuProducts = this.menuProductToMenuProductEntityConverter.convert( | ||
| menu | ||
| ); | ||
| if (menuEntity.id != null) { |
There was a problem hiding this comment.
식별자로 사용할 수 있는 seq가 있기 때문에 더 나은 방법으로도 구현이 가능하겠지만 일단은 @ElementCollection처럼 모두 삭제하고 새로 만드는 방식을 택했습니다.
| } | ||
|
|
||
| @Override | ||
| public List<Menu> findAll() { |
There was a problem hiding this comment.
현재 구현은 조회해온 menu의 수 만큼 추가로 쿼리하게 되므로 n+1 쿼리 문제가 있긴 합니다.
| public void setPrice(final Price price) { | ||
| if (!MenuDisplayPolicy.isDisplayable(this)) { | ||
| this.hide(); | ||
| throw new IllegalArgumentException("메뉴 노출 정책에 따라 가격을 변경할 수 없습니다"); |
There was a problem hiding this comment.
이전 과제에서 모델링할 당시 가격 변경시 정책을 위반하게 되면 메뉴를 숨기도록 해서 이를 따라 구현했는데, 정책을 위반하도록 가격을 변경할 수는 없는 것이 맞는 것으로 판단되어(기존 구현이 그렇게 되어 있으므로) 그렇게 변경했습니다.
|
|
||
| @PostMapping | ||
| public ResponseEntity<MenuGroup> create(@RequestBody final MenuGroup request) { | ||
| public ResponseEntity<MenuGroup> create(@RequestBody final CreateMenuGroupCommand request) { |
There was a problem hiding this comment.
도메인 모델을 그대로 받는 대신 요청하는 행위에 맞는 DTO를 도입해야 할 필요성이 느껴져 DTO를 추가했습니다.
| throw new IllegalArgumentException(); | ||
| } | ||
| public MenuGroup create(final CreateMenuGroupCommand request) { | ||
| final Name name = this.nameFactory.create(request.name); |
There was a problem hiding this comment.
이제 DTO를 받은 뒤 제대로 NameFactory를 사용해 Name을 생성하게 됩니다.
| this.price = price; | ||
| this.menuGroup = menuGroup; | ||
| this.menuProducts = menuProducts; | ||
| if (!MenuDisplayPolicy.isDisplayable(this)) { |
There was a problem hiding this comment.
테스트코드를 보니 메뉴 노출 정책에 부합하지 않으면 메뉴 생성 자체가 불가능해야 하는 것으로 보여 Menu의 생성 시점에 이를 검사하도록 했습니다.
There was a problem hiding this comment.
MenuDisplayPolicy를 static 메소드의 호출로 검증하는것과 파라미터 인자로 받아 검증하는것은 어떤 차이가 있을까요 ?
| assertThat(actual.displayed()).isTrue(); | ||
| } | ||
|
|
||
| @Disabled |
There was a problem hiding this comment.
그런데 이 경우 메뉴의 가격이 상품 가격의 합보다 높으면 메뉴 생성 자체가 불가능하므로 일부 케이스에 대한 테스트가 불가능해져서 어떻게 해결하면 좋을지 고민입니다.
| if (productPrices.size() != menuProductRequests.size()) { | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| final List<MenuProduct> menuProducts = menuProductRequests.stream() |
There was a problem hiding this comment.
기존 구현에는 command에 포함된 가격(아마 클라이언트에서 요청한 가격)을 그대로 사용한다는 문제점이 있었습니다.
생성 시점의 상품 가격을 직접 조회하여 반영하도록 개선했습니다.
| @Component | ||
| public static class MenuGroupToMenuGroupEntityConverter | ||
| implements Converter<MenuGroup, MenuGroupEntity> { | ||
| public static class MenuGroupToMenuGroupEntityConverter { |
sah3122
left a comment
There was a problem hiding this comment.
메뉴 리팩터링 구현 잘해주셨습니다 👍
몇가지 코멘트 남겨두었는데 확인 부탁드려요 !
추가로 남아있는 미션을 조급하게 진행하지 않아도 됩니다 ㅎㅎ
강의 기간이 끝나도 리뷰는 진행할수 있습니다 😄
|
|
||
| @PostMapping | ||
| public ResponseEntity<MenuGroup> create(@RequestBody final MenuGroup request) { | ||
| public ResponseEntity<MenuGroup> create(@RequestBody final CreateMenuGroupCommand request) { |
| throw new IllegalArgumentException(); | ||
| } | ||
| final List<Product> products = productRepository.findAllByIdIn( | ||
| final Map<UUID, Price> productPrices = productRepository.findAllByIdIn( |
There was a problem hiding this comment.
Menu Application Layer에서 Product Context와의 결합을 제거할수 있는 방법을 고민해보세요 😄
| final MenuGroup menuGroup = menuGroupRepository.findById(request.getMenuGroupId()) | ||
| public Menu create(final CreateMenuCommand command) { | ||
| final Price price = new Price(command.price); | ||
| final MenuGroup menuGroup = menuGroupRepository.findById(command.menuGroupId) |
There was a problem hiding this comment.
동일한 Context에서 Root Aggregate간 결합을 허용할건지, 결합을 제거할건지 고민해보시는것도 권장 드립니다 😄
| public interface MenuGroupRepository { | ||
|
|
||
| MenuGroup save(MenuGroup menuGroup); | ||
| MenuGroup save(final MenuGroup menuGroup); |
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class MenuDisplayPolicy { |
There was a problem hiding this comment.
현재 구현해주신 코드는 Menu Aggregate만을 사용하게 되지만, 가격 변경시 메뉴에 속한 상품의 합보다 적거나 같아야한다.와 같은 정책이 지켜지지 않을것 같아요.
메뉴가 가지고 있는 가격이 현시점에 상품 가격이라고 확신할수 없는 구조 입니다.
| this.displayed = false; | ||
| } | ||
|
|
||
| public void setPrice(final Price price) { |
There was a problem hiding this comment.
setter 네이밍을 사용하기 보다는 의미있는 네이밍을 고민해보세요 😄
| this.price = price; | ||
| this.menuGroup = menuGroup; | ||
| this.menuProducts = menuProducts; | ||
| if (!MenuDisplayPolicy.isDisplayable(this)) { |
There was a problem hiding this comment.
MenuDisplayPolicy를 static 메소드의 호출로 검증하는것과 파라미터 인자로 받아 검증하는것은 어떤 차이가 있을까요 ?
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| public class MenuEntityConverter { |
There was a problem hiding this comment.
entity domain 간 분리를 위한 converter 도입 좋습니다 ㅎㅎ
|
|
||
| public MenuEntity convert(final Menu menu) { | ||
| final MenuEntity menuEntity = new MenuEntity(); | ||
| menuEntity.id = menu.id; |
시간이 얼마 남지 않았다보니 마음이 급해 충분히 잘 되었을지 모르겠네요..
그래도 최선을 다해보았습니다.