Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions REQUIREMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## 도메인 주도 설계 기본 요소
### 1단계 - 리팩터링(상품)
- 키친포스의 요구 사항과 용어 사전, 모델링을 기반으로 상품 CONTEXT를 리팩터링한다.
- 상품 CONTEXT의 도메인 계층만 먼저 구현한다.
- products 패키지 밑에 tobe.domain 패키지를 만들고 거기서부터 구현을 시작한다.
- 용어 사전과 모델링이 부자연스럽거나 불완전하거나 잘못된 경우 지속적으로 수정한다.
- 새로운 모델에 맞게끔 클래스, 메서드, 모듈의 이름을 다시 지으면서 코드를 리팩터링한다.
- REPOSITORY 구현 시 자신에게 익숙하고 편한 것을 선택하여 진행한다.
76 changes: 50 additions & 26 deletions src/main/java/kitchenpos/menus/application/MenuService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import kitchenpos.menus.domain.MenuProduct;
import kitchenpos.menus.domain.MenuRepository;
import kitchenpos.products.domain.Product;
import kitchenpos.products.domain.ProductRecord;
import kitchenpos.products.domain.ProductRepository;
import kitchenpos.products.infra.PurgomalumClient;
import kitchenpos.products.infra.CheckBadWordClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -23,18 +24,18 @@ public class MenuService {
private final MenuRepository menuRepository;
private final MenuGroupRepository menuGroupRepository;
private final ProductRepository productRepository;
private final PurgomalumClient purgomalumClient;
private final CheckBadWordClient checkBadWordClient;

public MenuService(
final MenuRepository menuRepository,
final MenuGroupRepository menuGroupRepository,
final ProductRepository productRepository,
final PurgomalumClient purgomalumClient
final MenuRepository menuRepository,
final MenuGroupRepository menuGroupRepository,
final ProductRepository productRepository,
final CheckBadWordClient checkBadWordClient
) {
this.menuRepository = menuRepository;
this.menuGroupRepository = menuGroupRepository;
this.productRepository = productRepository;
this.purgomalumClient = purgomalumClient;
this.checkBadWordClient = checkBadWordClient;
}

@Transactional
Expand All @@ -44,16 +45,20 @@ public Menu create(final Menu request) {
throw new IllegalArgumentException();
}
final MenuGroup menuGroup = menuGroupRepository.findById(request.getMenuGroupId())
.orElseThrow(NoSuchElementException::new);
.orElseThrow(NoSuchElementException::new);
final List<MenuProduct> menuProductRequests = request.getMenuProducts();
if (Objects.isNull(menuProductRequests) || menuProductRequests.isEmpty()) {
throw new IllegalArgumentException();
}
final List<Product> products = productRepository.findAllByIdIn(
menuProductRequests.stream()
.map(MenuProduct::getProductId)
.toList()
// TODO, remove this line
final List<Product> tempProducts = productRepository.findAllByIdIn(
menuProductRequests.stream()
.map(MenuProduct::getProductId)
.toList()
);
final List<ProductRecord> products = tempProducts.stream()
.map(product -> new ProductRecord(product.getId(), product.getName(), product.getPrice()))
.toList();
if (products.size() != menuProductRequests.size()) {
throw new IllegalArgumentException();
}
Expand All @@ -64,11 +69,12 @@ public Menu create(final Menu request) {
if (quantity < 0) {
throw new IllegalArgumentException();
}
final Product product = productRepository.findById(menuProductRequest.getProductId())
.orElseThrow(NoSuchElementException::new);
final Product tempProduct = productRepository.findById(menuProductRequest.getProductId())
.orElseThrow(NoSuchElementException::new);
final ProductRecord product = new ProductRecord(tempProduct.getId(), tempProduct.getName(), tempProduct.getPrice());
sum = sum.add(
product.getPrice()
.multiply(BigDecimal.valueOf(quantity))
product.getPrice()
.multiply(BigDecimal.valueOf(quantity))
);
final MenuProduct menuProduct = new MenuProduct();
menuProduct.setProduct(product);
Expand All @@ -79,7 +85,7 @@ public Menu create(final Menu request) {
throw new IllegalArgumentException();
}
final String name = request.getName();
if (Objects.isNull(name) || purgomalumClient.containsProfanity(name)) {
if (Objects.isNull(name) || checkBadWordClient.containsProfanity(name)) {
throw new IllegalArgumentException();
}
final Menu menu = new Menu();
Expand All @@ -99,13 +105,13 @@ public Menu changePrice(final UUID menuId, final Menu request) {
throw new IllegalArgumentException();
}
final Menu menu = menuRepository.findById(menuId)
.orElseThrow(NoSuchElementException::new);
.orElseThrow(NoSuchElementException::new);
BigDecimal sum = BigDecimal.ZERO;
for (final MenuProduct menuProduct : menu.getMenuProducts()) {
sum = sum.add(
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
);
}
if (price.compareTo(sum) > 0) {
Expand All @@ -118,13 +124,13 @@ public Menu changePrice(final UUID menuId, final Menu request) {
@Transactional
public Menu display(final UUID menuId) {
final Menu menu = menuRepository.findById(menuId)
.orElseThrow(NoSuchElementException::new);
.orElseThrow(NoSuchElementException::new);
BigDecimal sum = BigDecimal.ZERO;
for (final MenuProduct menuProduct : menu.getMenuProducts()) {
sum = sum.add(
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
menuProduct.getProduct()
.getPrice()
.multiply(BigDecimal.valueOf(menuProduct.getQuantity()))
);
}
if (menu.getPrice().compareTo(sum) > 0) {
Expand All @@ -137,7 +143,7 @@ public Menu display(final UUID menuId) {
@Transactional
public Menu hide(final UUID menuId) {
final Menu menu = menuRepository.findById(menuId)
.orElseThrow(NoSuchElementException::new);
.orElseThrow(NoSuchElementException::new);
menu.setDisplayed(false);
return menu;
}
Expand All @@ -146,4 +152,22 @@ public Menu hide(final UUID menuId) {
public List<Menu> findAll() {
return menuRepository.findAll();
}

@Transactional
public void updateMenuDisplay(Product product) {
final List<Menu> menus = menuRepository.findAllByProductId(product.getId());
for (final Menu menu : menus) {
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) {
menu.setDisplayed(false);
}
}
}
}
8 changes: 4 additions & 4 deletions src/main/java/kitchenpos/menus/domain/MenuProduct.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import kitchenpos.products.domain.Product;
import kitchenpos.products.domain.ProductRecord;

import java.util.UUID;

Expand All @@ -28,7 +28,7 @@ public class MenuProduct {
columnDefinition = "binary(16)",
foreignKey = @ForeignKey(name = "fk_menu_product_to_product")
)
private Product product;
private ProductRecord product;

@Column(name = "quantity", nullable = false)
private long quantity;
Expand All @@ -47,11 +47,11 @@ public void setSeq(final Long seq) {
this.seq = seq;
}

public Product getProduct() {
public ProductRecord getProduct() {
return product;
}

public void setProduct(final Product product) {
public void setProduct(final ProductRecord product) {
this.product = product;
}

Expand Down
80 changes: 35 additions & 45 deletions src/main/java/kitchenpos/products/application/ProductService.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package kitchenpos.products.application;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이번 미션 항목들은 각 kitchenpos.products.tobe.* 위치해야해요~!


import kitchenpos.menus.application.MenuService;
import kitchenpos.menus.domain.Menu;
import kitchenpos.menus.domain.MenuProduct;
import kitchenpos.menus.domain.MenuRepository;
import kitchenpos.products.domain.Product;
import kitchenpos.products.domain.ProductName;
import kitchenpos.products.domain.ProductPrice;
import kitchenpos.products.domain.ProductRepository;
import kitchenpos.products.infra.PurgomalumClient;
import kitchenpos.products.infra.CheckBadWordClient;
import kitchenpos.products.ui.request.ChangePriceRequest;
import kitchenpos.products.ui.request.CreateProductRequest;
import kitchenpos.products.ui.response.ProductResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -18,64 +24,48 @@
@Service
public class ProductService {
private final ProductRepository productRepository;
private final MenuRepository menuRepository;
private final PurgomalumClient purgomalumClient;
private final CheckBadWordClient checkBadWordClient;
private final MenuService menuService;

public ProductService(
final ProductRepository productRepository,
final MenuRepository menuRepository,
final PurgomalumClient purgomalumClient
final ProductRepository productRepository,
final CheckBadWordClient checkBadWordClient,
final MenuService menuService
) {
this.productRepository = productRepository;
this.menuRepository = menuRepository;
this.purgomalumClient = purgomalumClient;
this.checkBadWordClient = checkBadWordClient;
this.menuService = menuService;
}

@Transactional
public Product create(final Product request) {
final BigDecimal price = request.getPrice();
if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException();
public ProductResponse create(final CreateProductRequest request) {
if (checkBadWordClient.containsProfanity(request.getName())) {
throw new IllegalArgumentException(
String.format("""
Product name contains profanity (name: %s)
""", request.getName())
);
}
final String name = request.getName();
if (Objects.isNull(name) || purgomalumClient.containsProfanity(name)) {
throw new IllegalArgumentException();
}
final Product product = new Product();
product.setId(UUID.randomUUID());
product.setName(name);
product.setPrice(price);
return productRepository.save(product);
final Product product = Product.Companion.create(request.getName(), request.getPrice());
final Product savedProduct = productRepository.save(product);
return ProductResponse.of(savedProduct);
}

@Transactional
public Product changePrice(final UUID productId, final Product request) {
final BigDecimal price = request.getPrice();
if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException();
}
public ProductResponse changePrice(final UUID productId, final ChangePriceRequest request) {
final Product product = productRepository.findById(productId)
.orElseThrow(NoSuchElementException::new);
product.setPrice(price);
final List<Menu> menus = menuRepository.findAllByProductId(productId);
for (final Menu menu : menus) {
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) {
menu.setDisplayed(false);
}
}
return product;
.orElseThrow(NoSuchElementException::new);
final Product updatedProduct = productRepository.save(product.changePrice(request.getPrice()));
menuService.updateMenuDisplay(updatedProduct);
return ProductResponse.of(productRepository.save(updatedProduct));
}


@Transactional(readOnly = true)
public List<Product> findAll() {
return productRepository.findAll();
public List<ProductResponse> findAll() {
final List<Product> products = productRepository.findAll();
return products.stream()
.map(ProductResponse::of)
.toList();
}
}

This file was deleted.

36 changes: 36 additions & 0 deletions src/main/java/kitchenpos/products/domain/Product.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kitchenpos.products.domain

import java.math.BigDecimal
import java.util.*

data class Product(
val id: UUID,
private val name: ProductName,
private val price: ProductPrice,
) {
fun getName(): String {
return name.name
}

fun getPrice(): BigDecimal {
return price.price
}

fun changePrice(price: BigDecimal): Product {
return Product(
id,
name,
ProductPrice.create(price)
)
}

companion object {
fun create(name: String, price: BigDecimal): Product {
return Product(
UUID.randomUUID(),
ProductName.create(name),
ProductPrice.create(price)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kitchenpos.products.domain

import org.springframework.data.jpa.repository.JpaRepository
import java.util.*

interface ProductJpaRepository : JpaRepository<ProductRecord, UUID>
18 changes: 18 additions & 0 deletions src/main/java/kitchenpos/products/domain/ProductName.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package kitchenpos.products.domain

class ProductName private constructor(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

VO로 분리해주셨네요~! 👍

val name: String,
) {
companion object {
fun create(value: String): ProductName {
validateNull(value)
return ProductName(value)
}

private fun validateNull(value: String) {
if (value.isBlank()) {
throw IllegalArgumentException("Invalid product name: $value")
}
}
}
}
Loading