Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bde7ea3
Price에 multiply() 추가
chr0m3 Oct 22, 2022
86b5760
MenuGroup entity 추가
chr0m3 Oct 22, 2022
4022276
MenuGroupEntity 추가
chr0m3 Oct 22, 2022
8382a9b
MenuGroupEntityConverter 추가
chr0m3 Oct 22, 2022
a1644b4
MenuGroupRepository 추가
chr0m3 Oct 22, 2022
b84cfe6
JpaMenuGroupRepository, JpaMenuGroupDao 추가
chr0m3 Oct 22, 2022
eb121a6
Price.multiply() 테스트 추가
chr0m3 Oct 22, 2022
4e44a8f
Price에 add() 추가
chr0m3 Oct 22, 2022
2c52931
Price가 Comparable을 구현하도록 수정
chr0m3 Oct 22, 2022
0146904
MenuProductQuantity VO 추가
chr0m3 Oct 22, 2022
a968bc6
MenuProduct VO 추가
chr0m3 Oct 23, 2022
7ff9973
MenuGroup VO 추가
chr0m3 Oct 23, 2022
3621adb
Menu entity 추가
chr0m3 Oct 23, 2022
5aab011
MenuDisplayPolicy 추가
chr0m3 Oct 23, 2022
58bd527
MenuRepository 추가
chr0m3 Oct 23, 2022
0701e17
Menu의 isVisible을 displayed로 변경
chr0m3 Oct 23, 2022
bf9bfb1
MenuEntity 추가
chr0m3 Oct 23, 2022
44adb96
MenuGroup VO 삭제
chr0m3 Oct 24, 2022
2d16e74
MenuProductEntity 추가
chr0m3 Oct 24, 2022
218f580
MenuProductEntityConverter 추가
chr0m3 Oct 24, 2022
c346395
누락된 final 추가
chr0m3 Oct 24, 2022
26aa5e7
MenuEntityConverter 추가
chr0m3 Oct 24, 2022
ac684f6
JpaMenuRepository 추가
chr0m3 Oct 24, 2022
748ee71
JpaMenuRepository 리팩터링 - 중복 코드 제거
chr0m3 Oct 24, 2022
96fe49a
메뉴 노출 정책을 위반하는 가격 변경이 불가능하도록 수정
chr0m3 Oct 24, 2022
f795b5c
새로운 도메인 모델을 사용하도록 Menu 컨텍스트 수정
chr0m3 Oct 24, 2022
2c7035e
불필요해진 과거 코드 제거
chr0m3 Oct 24, 2022
a662282
CreateMenuGroupCommand DTO 추가
chr0m3 Oct 24, 2022
a753755
ChangeMenuPriceCommand DTO 추가
chr0m3 Oct 24, 2022
86dc9e6
CreateMenuCommand DTO 추가
chr0m3 Oct 24, 2022
81afbbc
메뉴 노출 정책을 위반하는 메뉴 생성이 불가능하도록 수정
chr0m3 Oct 24, 2022
bd08ccb
메뉴 생성시 정확한 상품 가격을 반영하도록 개선
chr0m3 Oct 24, 2022
71f200a
DB 마이그레이션
chr0m3 Oct 24, 2022
139c6f8
도메인 모델 수정
chr0m3 Oct 24, 2022
1c460a0
Converter 인터페이스 제거
chr0m3 Oct 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/main/java/kitchenpos/common/price/Price.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* <li>가격은 음수일 수 없다.</li>
* </ul>
*/
public class Price {
public class Price implements Comparable<Price> {

public final BigDecimal value;

Expand All @@ -30,6 +30,23 @@ public Price(double value) {
this(BigDecimal.valueOf(value));
}

public Price add(Price augend) {
return new Price(this.value.add(augend.value));
}

public Price multiply(BigDecimal multiplicand) {
return new Price(this.value.multiply(multiplicand));
}

public Price multiply(long multiplicand) {
return this.multiply(BigDecimal.valueOf(multiplicand));
}

@Override
public int compareTo(Price o) {
return this.value.compareTo(o.value);
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import java.net.URI;
import java.util.List;
import kitchenpos.menu.application.MenuGroupService;
import kitchenpos.menu.domain.MenuGroup;
import kitchenpos.menu.tobe.application.dto.CreateMenuGroupCommand;
import kitchenpos.menu.tobe.domain.entity.MenuGroup;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -21,9 +22,9 @@ public MenuGroupRestController(final MenuGroupService menuGroupService) {
}

@PostMapping
public ResponseEntity<MenuGroup> create(@RequestBody final MenuGroup request) {
public ResponseEntity<MenuGroup> create(@RequestBody final CreateMenuGroupCommand request) {
Copy link
Author

Choose a reason for hiding this comment

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

도메인 모델을 그대로 받는 대신 요청하는 행위에 맞는 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 도입 좋습니다 ㅎㅎ 😄

final MenuGroup response = menuGroupService.create(request);
return ResponseEntity.created(URI.create("/api/menu-groups/" + response.getId()))
return ResponseEntity.created(URI.create("/api/menu-groups/" + response.id))
.body(response);
}

Expand Down
20 changes: 15 additions & 5 deletions src/main/java/kitchenpos/menu/api/MenuRestController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.util.List;
import java.util.UUID;
import kitchenpos.menu.application.MenuService;
import kitchenpos.menu.domain.Menu;
import kitchenpos.menu.tobe.application.dto.ChangeMenuPriceCommand;
import kitchenpos.menu.tobe.application.dto.CreateMenuCommand;
import kitchenpos.menu.tobe.domain.entity.Menu;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -17,22 +19,30 @@
@RequestMapping("/api/menus")
@RestController
public class MenuRestController {

private final MenuService menuService;

public MenuRestController(final MenuService menuService) {
this.menuService = menuService;
}

@PostMapping
public ResponseEntity<Menu> create(@RequestBody final Menu request) {
public ResponseEntity<Menu> create(@RequestBody final CreateMenuCommand request) {
final Menu response = menuService.create(request);
return ResponseEntity.created(URI.create("/api/menus/" + response.getId()))
return ResponseEntity.created(URI.create("/api/menus/" + response.id))
.body(response);
}

@PutMapping("/{menuId}/price")
public ResponseEntity<Menu> changePrice(@PathVariable final UUID menuId, @RequestBody final Menu request) {
return ResponseEntity.ok(menuService.changePrice(menuId, request));
public ResponseEntity<Menu> changePrice(
@PathVariable final UUID menuId,
@RequestBody final Menu request
) {
final ChangeMenuPriceCommand command = new ChangeMenuPriceCommand(
menuId,
request.price().value
);
return ResponseEntity.ok(menuService.changePrice(command));
}

@PutMapping("/{menuId}/display")
Expand Down
27 changes: 15 additions & 12 deletions src/main/java/kitchenpos/menu/application/MenuGroupService.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
package kitchenpos.menu.application;

import java.util.List;
import java.util.Objects;
import java.util.UUID;
import kitchenpos.menu.domain.MenuGroup;
import kitchenpos.menu.domain.MenuGroupRepository;
import kitchenpos.common.name.Name;
import kitchenpos.common.name.NameFactory;
import kitchenpos.menu.tobe.application.dto.CreateMenuGroupCommand;
import kitchenpos.menu.tobe.domain.entity.MenuGroup;
import kitchenpos.menu.tobe.domain.repository.MenuGroupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MenuGroupService {

private final MenuGroupRepository menuGroupRepository;

public MenuGroupService(final MenuGroupRepository menuGroupRepository) {
private final NameFactory nameFactory;

@Autowired
public MenuGroupService(MenuGroupRepository menuGroupRepository, NameFactory nameFactory) {
this.menuGroupRepository = menuGroupRepository;
this.nameFactory = nameFactory;
}

@Transactional
public MenuGroup create(final MenuGroup request) {
final String name = request.getName();
if (Objects.isNull(name) || name.isEmpty()) {
throw new IllegalArgumentException();
}
final MenuGroup menuGroup = new MenuGroup();
menuGroup.setId(UUID.randomUUID());
menuGroup.setName(name);
public MenuGroup create(final CreateMenuGroupCommand request) {
final Name name = this.nameFactory.create(request.name);
Copy link
Author

Choose a reason for hiding this comment

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

이제 DTO를 받은 뒤 제대로 NameFactory를 사용해 Name을 생성하게 됩니다.

final MenuGroup menuGroup = new MenuGroup(UUID.randomUUID(), name);
return menuGroupRepository.save(menuGroup);
}

Expand Down
141 changes: 54 additions & 87 deletions src/main/java/kitchenpos/menu/application/MenuService.java
Original file line number Diff line number Diff line change
@@ -1,116 +1,94 @@
package kitchenpos.menu.application;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import kitchenpos.common.profanity.domain.ProfanityDetectService;
import kitchenpos.menu.domain.Menu;
import kitchenpos.menu.domain.MenuGroup;
import kitchenpos.menu.domain.MenuGroupRepository;
import kitchenpos.menu.domain.MenuProduct;
import kitchenpos.menu.domain.MenuRepository;
import kitchenpos.product.domain.Product;
import kitchenpos.product.domain.ProductRepository;
import kitchenpos.common.name.Name;
import kitchenpos.common.name.NameFactory;
import kitchenpos.common.price.Price;
import kitchenpos.menu.tobe.application.dto.ChangeMenuPriceCommand;
import kitchenpos.menu.tobe.application.dto.CreateMenuCommand;
import kitchenpos.menu.tobe.domain.entity.Menu;
import kitchenpos.menu.tobe.domain.entity.MenuGroup;
import kitchenpos.menu.tobe.domain.repository.MenuGroupRepository;
import kitchenpos.menu.tobe.domain.repository.MenuRepository;
import kitchenpos.menu.tobe.domain.vo.MenuProduct;
import kitchenpos.product.tobe.domain.entity.Product;
import kitchenpos.product.tobe.domain.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MenuService {

private final MenuRepository menuRepository;

private final MenuGroupRepository menuGroupRepository;

private final ProductRepository productRepository;
private final ProfanityDetectService profanityDetectService;

private final NameFactory nameFactory;

@Autowired
public MenuService(
final MenuRepository menuRepository,
final MenuGroupRepository menuGroupRepository,
final ProductRepository productRepository,
final ProfanityDetectService profanityDetectService
final NameFactory nameFactory
) {
this.menuRepository = menuRepository;
this.menuGroupRepository = menuGroupRepository;
this.productRepository = productRepository;
this.profanityDetectService = profanityDetectService;
this.nameFactory = nameFactory;
}

@Transactional
public Menu create(final Menu request) {
final BigDecimal price = request.getPrice();
if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException();
}
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)
Copy link

Choose a reason for hiding this comment

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

동일한 Context에서 Root Aggregate간 결합을 허용할건지, 결합을 제거할건지 고민해보시는것도 권장 드립니다 😄

.orElseThrow(NoSuchElementException::new);
final List<MenuProduct> menuProductRequests = request.getMenuProducts();
final List<MenuProduct> menuProductRequests = command.menuProducts;
if (Objects.isNull(menuProductRequests) || menuProductRequests.isEmpty()) {
throw new IllegalArgumentException();
}
final List<Product> products = productRepository.findAllByIdIn(
final Map<UUID, Price> productPrices = productRepository.findAllByIdIn(
Copy link

Choose a reason for hiding this comment

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

Menu Application Layer에서 Product Context와의 결합을 제거할수 있는 방법을 고민해보세요 😄

menuProductRequests.stream()
.map(MenuProduct::getProductId)
.collect(Collectors.toList())
);
if (products.size() != menuProductRequests.size()) {
throw new IllegalArgumentException();
}
final List<MenuProduct> menuProducts = new ArrayList<>();
BigDecimal sum = BigDecimal.ZERO;
for (final MenuProduct menuProductRequest : menuProductRequests) {
final long quantity = menuProductRequest.getQuantity();
if (quantity < 0) {
throw new IllegalArgumentException();
}
final Product product = productRepository.findById(menuProductRequest.getProductId())
.orElseThrow(NoSuchElementException::new);
sum = sum.add(
product.getPrice()
.multiply(BigDecimal.valueOf(quantity))
);
final MenuProduct menuProduct = new MenuProduct();
menuProduct.setProduct(product);
menuProduct.setQuantity(quantity);
menuProducts.add(menuProduct);
}
if (price.compareTo(sum) > 0) {
throw new IllegalArgumentException();
}
final String name = request.getName();
if (Objects.isNull(name) || profanityDetectService.profanityIn(name)) {
.map(menuProduct -> menuProduct.productId)
.collect(Collectors.toUnmodifiableList())
)
.stream()
.collect(Collectors.toMap(product -> product.id, Product::price));
if (productPrices.size() != menuProductRequests.size()) {
throw new IllegalArgumentException();
}
final Menu menu = new Menu();
menu.setId(UUID.randomUUID());
menu.setName(name);
menu.setPrice(price);
menu.setMenuGroup(menuGroup);
menu.setDisplayed(request.isDisplayed());
menu.setMenuProducts(menuProducts);
final List<MenuProduct> menuProducts = menuProductRequests.stream()
Copy link
Author

Choose a reason for hiding this comment

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

기존 구현에는 command에 포함된 가격(아마 클라이언트에서 요청한 가격)을 그대로 사용한다는 문제점이 있었습니다.
생성 시점의 상품 가격을 직접 조회하여 반영하도록 개선했습니다.

.map(menuProduct -> new MenuProduct(
menuProduct.productId,
productPrices.get(menuProduct.productId),
menuProduct.quantity
))
.collect(Collectors.toUnmodifiableList());
final Name name = this.nameFactory.create(command.name);
final Menu menu = new Menu(
UUID.randomUUID(),
name,
command.displayed,
price,
menuGroup,
menuProducts
);
return menuRepository.save(menu);
}

@Transactional
public Menu changePrice(final UUID menuId, final Menu request) {
final BigDecimal price = request.getPrice();
if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException();
}
final Menu menu = menuRepository.findById(menuId)
public Menu changePrice(final ChangeMenuPriceCommand command) {
final Price price = new Price(command.price);
final Menu menu = menuRepository.findById(command.id)
.orElseThrow(NoSuchElementException::new);
BigDecimal sum = BigDecimal.ZERO;
for (final MenuProduct menuProduct : menu.getMenuProducts()) {
sum = sum.add(
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
);
}
if (price.compareTo(sum) > 0) {
throw new IllegalArgumentException();
}
menu.setPrice(price);
return menu;
}
Expand All @@ -119,26 +97,15 @@ public Menu changePrice(final UUID menuId, final Menu request) {
public Menu display(final UUID menuId) {
final Menu menu = menuRepository.findById(menuId)
.orElseThrow(NoSuchElementException::new);
BigDecimal sum = BigDecimal.ZERO;
for (final MenuProduct menuProduct : menu.getMenuProducts()) {
sum = sum.add(
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
);
}
if (menu.getPrice().compareTo(sum) > 0) {
throw new IllegalStateException();
}
menu.setDisplayed(true);
menu.display();
return menu;
}

@Transactional
public Menu hide(final UUID menuId) {
final Menu menu = menuRepository.findById(menuId)
.orElseThrow(NoSuchElementException::new);
menu.setDisplayed(false);
menu.hide();
return menu;
}

Expand Down
14 changes: 0 additions & 14 deletions src/main/java/kitchenpos/menu/domain/MenuGroupRepository.java

This file was deleted.

This file was deleted.

15 changes: 0 additions & 15 deletions src/main/java/kitchenpos/menu/infra/JpaMenuRepository.java

This file was deleted.

Loading