diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 000000000..37f621fa0 --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,8 @@ +## 도메인 주도 설계 기본 요소 +### 1단계 - 리팩터링(상품) +- 키친포스의 요구 사항과 용어 사전, 모델링을 기반으로 상품 CONTEXT를 리팩터링한다. +- 상품 CONTEXT의 도메인 계층만 먼저 구현한다. +- products 패키지 밑에 tobe.domain 패키지를 만들고 거기서부터 구현을 시작한다. +- 용어 사전과 모델링이 부자연스럽거나 불완전하거나 잘못된 경우 지속적으로 수정한다. +- 새로운 모델에 맞게끔 클래스, 메서드, 모듈의 이름을 다시 지으면서 코드를 리팩터링한다. +- REPOSITORY 구현 시 자신에게 익숙하고 편한 것을 선택하여 진행한다. diff --git a/src/main/java/kitchenpos/menus/application/MenuService.java b/src/main/java/kitchenpos/menus/application/MenuService.java index abefa5bcf..89d4441c6 100644 --- a/src/main/java/kitchenpos/menus/application/MenuService.java +++ b/src/main/java/kitchenpos/menus/application/MenuService.java @@ -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; @@ -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 @@ -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 menuProductRequests = request.getMenuProducts(); if (Objects.isNull(menuProductRequests) || menuProductRequests.isEmpty()) { throw new IllegalArgumentException(); } - final List products = productRepository.findAllByIdIn( - menuProductRequests.stream() - .map(MenuProduct::getProductId) - .toList() + // TODO, remove this line + final List tempProducts = productRepository.findAllByIdIn( + menuProductRequests.stream() + .map(MenuProduct::getProductId) + .toList() ); + final List products = tempProducts.stream() + .map(product -> new ProductRecord(product.getId(), product.getName(), product.getPrice())) + .toList(); if (products.size() != menuProductRequests.size()) { throw new IllegalArgumentException(); } @@ -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); @@ -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(); @@ -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) { @@ -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) { @@ -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; } @@ -146,4 +152,22 @@ public Menu hide(final UUID menuId) { public List findAll() { return menuRepository.findAll(); } + + @Transactional + public void updateMenuDisplay(Product product) { + final List 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); + } + } + } } diff --git a/src/main/java/kitchenpos/menus/domain/MenuProduct.java b/src/main/java/kitchenpos/menus/domain/MenuProduct.java index b47ca26cb..f8d146961 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuProduct.java +++ b/src/main/java/kitchenpos/menus/domain/MenuProduct.java @@ -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; @@ -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; @@ -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; } diff --git a/src/main/java/kitchenpos/products/application/ProductService.java b/src/main/java/kitchenpos/products/application/ProductService.java index 20cf63996..e630c6af9 100644 --- a/src/main/java/kitchenpos/products/application/ProductService.java +++ b/src/main/java/kitchenpos/products/application/ProductService.java @@ -1,11 +1,17 @@ package kitchenpos.products.application; +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; @@ -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 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 findAll() { - return productRepository.findAll(); + public List findAll() { + final List products = productRepository.findAll(); + return products.stream() + .map(ProductResponse::of) + .toList(); } } diff --git a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java b/src/main/java/kitchenpos/products/domain/JpaProductRepository.java deleted file mode 100644 index 90b069779..000000000 --- a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package kitchenpos.products.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.UUID; - -public interface JpaProductRepository extends ProductRepository, JpaRepository { -} diff --git a/src/main/java/kitchenpos/products/domain/Product.kt b/src/main/java/kitchenpos/products/domain/Product.kt new file mode 100644 index 000000000..b77e27817 --- /dev/null +++ b/src/main/java/kitchenpos/products/domain/Product.kt @@ -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) + ) + } + } +} diff --git a/src/main/java/kitchenpos/products/domain/ProductJpaRepository.kt b/src/main/java/kitchenpos/products/domain/ProductJpaRepository.kt new file mode 100644 index 000000000..dc33f7496 --- /dev/null +++ b/src/main/java/kitchenpos/products/domain/ProductJpaRepository.kt @@ -0,0 +1,6 @@ +package kitchenpos.products.domain + +import org.springframework.data.jpa.repository.JpaRepository +import java.util.* + +interface ProductJpaRepository : JpaRepository diff --git a/src/main/java/kitchenpos/products/domain/ProductName.kt b/src/main/java/kitchenpos/products/domain/ProductName.kt new file mode 100644 index 000000000..ff49fda12 --- /dev/null +++ b/src/main/java/kitchenpos/products/domain/ProductName.kt @@ -0,0 +1,18 @@ +package kitchenpos.products.domain + +class ProductName private constructor( + 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") + } + } + } +} diff --git a/src/main/java/kitchenpos/products/domain/ProductPrice.kt b/src/main/java/kitchenpos/products/domain/ProductPrice.kt new file mode 100644 index 000000000..ee514eb60 --- /dev/null +++ b/src/main/java/kitchenpos/products/domain/ProductPrice.kt @@ -0,0 +1,27 @@ +package kitchenpos.products.domain + +import java.math.BigDecimal + +class ProductPrice private constructor( + val price: BigDecimal +) { + companion object { + fun create(value: BigDecimal): ProductPrice { + validateNull(value) + validatePositive(value) + return ProductPrice(value) + } + + private fun validateNull(value: BigDecimal) { + if (value <= BigDecimal.ZERO) { + throw IllegalArgumentException("Invalid product price: $value") + } + } + + private fun validatePositive(value: BigDecimal) { + if (value < BigDecimal.ZERO) { + throw IllegalArgumentException("Invalid product price: $value") + } + } + } +} diff --git a/src/main/java/kitchenpos/products/domain/Product.java b/src/main/java/kitchenpos/products/domain/ProductRecord.java similarity index 79% rename from src/main/java/kitchenpos/products/domain/Product.java rename to src/main/java/kitchenpos/products/domain/ProductRecord.java index ee2a7dfa9..c9ff80d94 100644 --- a/src/main/java/kitchenpos/products/domain/Product.java +++ b/src/main/java/kitchenpos/products/domain/ProductRecord.java @@ -10,7 +10,7 @@ @Table(name = "product") @Entity -public class Product { +public class ProductRecord { @Column(name = "id", columnDefinition = "binary(16)") @Id private UUID id; @@ -21,30 +21,24 @@ public class Product { @Column(name = "price", nullable = false) private BigDecimal price; - public Product() { + public ProductRecord() { } - public UUID getId() { - return id; + public ProductRecord(UUID id, String name, BigDecimal price) { + this.id = id; + this.name = name; + this.price = price; } - public void setId(final UUID id) { - this.id = id; + public UUID getId() { + return id; } public String getName() { return name; } - public void setName(final String name) { - this.name = name; - } - public BigDecimal getPrice() { return price; } - - public void setPrice(final BigDecimal price) { - this.price = price; - } } diff --git a/src/main/java/kitchenpos/products/domain/ProductRepository.java b/src/main/java/kitchenpos/products/domain/ProductRepository.java index 3637e4232..ed30b2436 100644 --- a/src/main/java/kitchenpos/products/domain/ProductRepository.java +++ b/src/main/java/kitchenpos/products/domain/ProductRepository.java @@ -13,4 +13,3 @@ public interface ProductRepository { List findAllByIdIn(List ids); } - diff --git a/src/main/java/kitchenpos/products/domain/ProductRepositoryImpl.kt b/src/main/java/kitchenpos/products/domain/ProductRepositoryImpl.kt new file mode 100644 index 000000000..523fcc076 --- /dev/null +++ b/src/main/java/kitchenpos/products/domain/ProductRepositoryImpl.kt @@ -0,0 +1,41 @@ +package kitchenpos.products.domain + +import org.springframework.stereotype.Repository +import java.util.* + +@Repository +class ProductRepositoryImpl( + private val productJpaRepository: ProductJpaRepository +) : ProductRepository { + override fun save(product: Product): Product { + return productJpaRepository.save(product.toRecord()).toProduct() + } + + override fun findById(id: UUID?): Optional { + TODO("Not yet implemented") + } + + override fun findAll(): MutableList { + TODO("Not yet implemented") + } + + override fun findAllByIdIn(ids: MutableList?): MutableList { + TODO("Not yet implemented") + } +} + +fun Product.toRecord(): ProductRecord { + return ProductRecord( + id, + getName(), + getPrice(), + ) +} + +fun ProductRecord.toProduct(): Product { + return Product( + id = id, + name = ProductName.create(name), + price = ProductPrice.create(price) + ) +} diff --git a/src/main/java/kitchenpos/products/infra/PurgomalumClient.java b/src/main/java/kitchenpos/products/infra/CheckBadWordClient.java similarity index 68% rename from src/main/java/kitchenpos/products/infra/PurgomalumClient.java rename to src/main/java/kitchenpos/products/infra/CheckBadWordClient.java index 4002a2bb8..1930a52b1 100644 --- a/src/main/java/kitchenpos/products/infra/PurgomalumClient.java +++ b/src/main/java/kitchenpos/products/infra/CheckBadWordClient.java @@ -1,5 +1,5 @@ package kitchenpos.products.infra; -public interface PurgomalumClient { +public interface CheckBadWordClient { boolean containsProfanity(String text); } diff --git a/src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java b/src/main/java/kitchenpos/products/infra/DefaultCheckBadWordClient.java similarity index 82% rename from src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java rename to src/main/java/kitchenpos/products/infra/DefaultCheckBadWordClient.java index 87dba885c..b7261d343 100644 --- a/src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java +++ b/src/main/java/kitchenpos/products/infra/DefaultCheckBadWordClient.java @@ -8,10 +8,10 @@ import java.net.URI; @Component -public class DefaultPurgomalumClient implements PurgomalumClient { +public class DefaultCheckBadWordClient implements CheckBadWordClient { private final RestTemplate restTemplate; - public DefaultPurgomalumClient(final RestTemplateBuilder restTemplateBuilder) { + public DefaultCheckBadWordClient(final RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); } diff --git a/src/main/java/kitchenpos/products/ui/ProductRestController.java b/src/main/java/kitchenpos/products/ui/ProductRestController.java index c71c795a4..3e4f9ca09 100644 --- a/src/main/java/kitchenpos/products/ui/ProductRestController.java +++ b/src/main/java/kitchenpos/products/ui/ProductRestController.java @@ -2,6 +2,11 @@ import kitchenpos.products.application.ProductService; import kitchenpos.products.domain.Product; +import kitchenpos.products.domain.ProductName; +import kitchenpos.products.domain.ProductRecord; +import kitchenpos.products.ui.request.ChangePriceRequest; +import kitchenpos.products.ui.request.CreateProductRequest; +import kitchenpos.products.ui.response.ProductResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,19 +30,20 @@ public ProductRestController(final ProductService productService) { } @PostMapping - public ResponseEntity create(@RequestBody final Product request) { - final Product response = productService.create(request); + public ResponseEntity create(@RequestBody final CreateProductRequest request) { + final ProductResponse response = productService.create(request); return ResponseEntity.created(URI.create("/api/products/" + response.getId())) .body(response); } @PutMapping("/{productId}/price") - public ResponseEntity changePrice(@PathVariable final UUID productId, @RequestBody final Product request) { + public ResponseEntity changePrice(@PathVariable final UUID productId, @RequestBody final ChangePriceRequest request) { return ResponseEntity.ok(productService.changePrice(productId, request)); } @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(productService.findAll()); + public ResponseEntity> findAll() { + List products = productService.findAll(); + return ResponseEntity.ok(products); } } diff --git a/src/main/java/kitchenpos/products/ui/request/ChangePriceRequest.kt b/src/main/java/kitchenpos/products/ui/request/ChangePriceRequest.kt new file mode 100644 index 000000000..edca4c6d6 --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/request/ChangePriceRequest.kt @@ -0,0 +1,7 @@ +package kitchenpos.products.ui.request + +import java.math.BigDecimal + +data class ChangePriceRequest( + val price: BigDecimal +) diff --git a/src/main/java/kitchenpos/products/ui/request/CreateProductRequest.kt b/src/main/java/kitchenpos/products/ui/request/CreateProductRequest.kt new file mode 100644 index 000000000..84d1a6e09 --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/request/CreateProductRequest.kt @@ -0,0 +1,8 @@ +package kitchenpos.products.ui.request + +import java.math.BigDecimal + +data class CreateProductRequest( + val name: String, + val price: BigDecimal, +) diff --git a/src/main/java/kitchenpos/products/ui/response/ProductResponse.kt b/src/main/java/kitchenpos/products/ui/response/ProductResponse.kt new file mode 100644 index 000000000..343b0e375 --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/response/ProductResponse.kt @@ -0,0 +1,21 @@ +package kitchenpos.products.ui.response + +import kitchenpos.products.domain.Product +import java.math.BigDecimal + +data class ProductResponse( + val id: String, + val name: String, + val price: BigDecimal, +) { + companion object { + @JvmStatic + fun of(product: Product): ProductResponse { + return ProductResponse( + id = product.id.toString(), + name = product.getName(), + price = product.getPrice() + ) + } + } +} diff --git a/src/test/java/kitchenpos/Fixtures.java b/src/test/java/kitchenpos/Fixtures.java index 434768a52..6cf005a6a 100644 --- a/src/test/java/kitchenpos/Fixtures.java +++ b/src/test/java/kitchenpos/Fixtures.java @@ -9,6 +9,7 @@ import kitchenpos.menus.domain.MenuGroup; import kitchenpos.menus.domain.MenuProduct; import kitchenpos.products.domain.Product; +import kitchenpos.products.domain.ProductRecord; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -52,12 +53,12 @@ public static MenuGroup menuGroup(final String name) { public static MenuProduct menuProduct() { final MenuProduct menuProduct = new MenuProduct(); menuProduct.setSeq(new Random().nextLong()); - menuProduct.setProduct(product()); + menuProduct.setProduct(productRecord()); menuProduct.setQuantity(2L); return menuProduct; } - public static MenuProduct menuProduct(final Product product, final long quantity) { + public static MenuProduct menuProduct(final ProductRecord product, final long quantity) { final MenuProduct menuProduct = new MenuProduct(); menuProduct.setSeq(new Random().nextLong()); menuProduct.setProduct(product); @@ -121,11 +122,13 @@ public static Product product() { return product("후라이드", 16_000L); } + public static ProductRecord productRecord() { + return new ProductRecord(UUID.randomUUID(), "후라이드", BigDecimal.valueOf(16_000L)); + } + public static Product product(final String name, final long price) { - final Product product = new Product(); - product.setId(UUID.randomUUID()); - product.setName(name); - product.setPrice(BigDecimal.valueOf(price)); - return product; + return Product.Companion.create( + name, BigDecimal.valueOf(price) + ); } } diff --git a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java b/src/test/java/kitchenpos/menus/application/MenuServiceTest.java index 277679118..2ced35e89 100644 --- a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java +++ b/src/test/java/kitchenpos/menus/application/MenuServiceTest.java @@ -4,11 +4,12 @@ import kitchenpos.menus.domain.MenuGroupRepository; import kitchenpos.menus.domain.MenuProduct; import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.application.FakePurgomalumClient; +import kitchenpos.products.application.FakeCheckBadWordClient; import kitchenpos.products.application.InMemoryProductRepository; 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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,37 +39,40 @@ class MenuServiceTest { private MenuRepository menuRepository; private MenuGroupRepository menuGroupRepository; private ProductRepository productRepository; - private PurgomalumClient purgomalumClient; + private CheckBadWordClient checkBadWordClient; private MenuService menuService; private UUID menuGroupId; - private Product product; + private ProductRecord productRecord; @BeforeEach void setUp() { menuRepository = new InMemoryMenuRepository(); menuGroupRepository = new InMemoryMenuGroupRepository(); productRepository = new InMemoryProductRepository(); - purgomalumClient = new FakePurgomalumClient(); - menuService = new MenuService(menuRepository, menuGroupRepository, productRepository, purgomalumClient); + checkBadWordClient = new FakeCheckBadWordClient(); + menuService = new MenuService(menuRepository, menuGroupRepository, productRepository, checkBadWordClient); menuGroupId = menuGroupRepository.save(menuGroup()).getId(); - product = productRepository.save(product("후라이드", 16_000L)); + final Product product = productRepository.save(product("후라이드", 16_000L)); + productRecord = new ProductRecord( + product.getId(), product.getName(), product.getPrice() + ); } @DisplayName("1개 이상의 등록된 상품으로 메뉴를 등록할 수 있다.") @Test void create() { final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) + "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(productRecord.getId(), 2L) ); final Menu actual = menuService.create(expected); assertThat(actual).isNotNull(); assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()), - () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()), - () -> assertThat(actual.getMenuGroup().getId()).isEqualTo(expected.getMenuGroupId()), - () -> assertThat(actual.isDisplayed()).isEqualTo(expected.isDisplayed()), - () -> assertThat(actual.getMenuProducts()).hasSize(1) + () -> assertThat(actual.getId()).isNotNull(), + () -> assertThat(actual.getName()).isEqualTo(expected.getName()), + () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()), + () -> assertThat(actual.getMenuGroup().getId()).isEqualTo(expected.getMenuGroupId()), + () -> assertThat(actual.isDisplayed()).isEqualTo(expected.isDisplayed()), + () -> assertThat(actual.getMenuProducts()).hasSize(1) ); } @@ -78,14 +82,14 @@ void create() { void create(final List menuProducts) { final Menu expected = createMenuRequest("후라이드+후라이드", 19_000L, menuGroupId, true, menuProducts); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } private static List menuProducts() { return Arrays.asList( - null, - Arguments.of(Collections.emptyList()), - Arguments.of(Arrays.asList(createMenuProductRequest(INVALID_ID, 2L))) + null, + Arguments.of(Collections.emptyList()), + Arguments.of(Arrays.asList(createMenuProductRequest(INVALID_ID, 2L))) ); } @@ -93,10 +97,10 @@ private static List menuProducts() { @Test void createNegativeQuantity() { final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), -1L) + "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(productRecord.getId(), -1L) ); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴의 가격이 올바르지 않으면 등록할 수 없다.") @@ -105,20 +109,20 @@ void createNegativeQuantity() { @ParameterizedTest void create(final BigDecimal price) { final Menu expected = createMenuRequest( - "후라이드+후라이드", price, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) + "후라이드+후라이드", price, menuGroupId, true, createMenuProductRequest(productRecord.getId(), 2L) ); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다.") @Test void createExpensiveMenu() { final Menu expected = createMenuRequest( - "후라이드+후라이드", 33_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) + "후라이드+후라이드", 33_000L, menuGroupId, true, createMenuProductRequest(productRecord.getId(), 2L) ); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴는 특정 메뉴 그룹에 속해야 한다.") @@ -126,10 +130,10 @@ void createExpensiveMenu() { @ParameterizedTest void create(final UUID menuGroupId) { final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) + "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(productRecord.getId(), 2L) ); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(NoSuchElementException.class); + .isInstanceOf(NoSuchElementException.class); } @DisplayName("메뉴의 이름이 올바르지 않으면 등록할 수 없다.") @@ -138,16 +142,16 @@ void create(final UUID menuGroupId) { @ParameterizedTest void create(final String name) { final Menu expected = createMenuRequest( - name, 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) + name, 19_000L, menuGroupId, true, createMenuProductRequest(productRecord.getId(), 2L) ); assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴의 가격을 변경할 수 있다.") @Test void changePrice() { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(productRecord, 2L))).getId(); final Menu expected = changePriceRequest(16_000L); final Menu actual = menuService.changePrice(menuId, expected); assertThat(actual.getPrice()).isEqualTo(expected.getPrice()); @@ -158,25 +162,25 @@ void changePrice() { @NullSource @ParameterizedTest void changePrice(final BigDecimal price) { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(productRecord, 2L))).getId(); final Menu expected = changePriceRequest(price); assertThatThrownBy(() -> menuService.changePrice(menuId, expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다.") @Test void changePriceToExpensive() { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(productRecord, 2L))).getId(); final Menu expected = changePriceRequest(33_000L); assertThatThrownBy(() -> menuService.changePrice(menuId, expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("메뉴를 노출할 수 있다.") @Test void display() { - final UUID menuId = menuRepository.save(menu(19_000L, false, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(19_000L, false, menuProduct(productRecord, 2L))).getId(); final Menu actual = menuService.display(menuId); assertThat(actual.isDisplayed()).isTrue(); } @@ -184,15 +188,15 @@ void display() { @DisplayName("메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 높을 경우 메뉴를 노출할 수 없다.") @Test void displayExpensiveMenu() { - final UUID menuId = menuRepository.save(menu(33_000L, false, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(33_000L, false, menuProduct(productRecord, 2L))).getId(); assertThatThrownBy(() -> menuService.display(menuId)) - .isInstanceOf(IllegalStateException.class); + .isInstanceOf(IllegalStateException.class); } @DisplayName("메뉴를 숨길 수 있다.") @Test void hide() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))).getId(); + final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct(productRecord, 2L))).getId(); final Menu actual = menuService.hide(menuId); assertThat(actual.isDisplayed()).isFalse(); } @@ -200,47 +204,47 @@ void hide() { @DisplayName("메뉴의 목록을 조회할 수 있다.") @Test void findAll() { - menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))); + menuRepository.save(menu(19_000L, true, menuProduct(productRecord, 2L))); final List actual = menuService.findAll(); assertThat(actual).hasSize(1); } private Menu createMenuRequest( - final String name, - final long price, - final UUID menuGroupId, - final boolean displayed, - final MenuProduct... menuProducts + final String name, + final long price, + final UUID menuGroupId, + final boolean displayed, + final MenuProduct... menuProducts ) { return createMenuRequest(name, BigDecimal.valueOf(price), menuGroupId, displayed, menuProducts); } private Menu createMenuRequest( - final String name, - final BigDecimal price, - final UUID menuGroupId, - final boolean displayed, - final MenuProduct... menuProducts + final String name, + final BigDecimal price, + final UUID menuGroupId, + final boolean displayed, + final MenuProduct... menuProducts ) { return createMenuRequest(name, price, menuGroupId, displayed, Arrays.asList(menuProducts)); } private Menu createMenuRequest( - final String name, - final long price, - final UUID menuGroupId, - final boolean displayed, - final List menuProducts + final String name, + final long price, + final UUID menuGroupId, + final boolean displayed, + final List menuProducts ) { return createMenuRequest(name, BigDecimal.valueOf(price), menuGroupId, displayed, menuProducts); } private Menu createMenuRequest( - final String name, - final BigDecimal price, - final UUID menuGroupId, - final boolean displayed, - final List menuProducts + final String name, + final BigDecimal price, + final UUID menuGroupId, + final boolean displayed, + final List menuProducts ) { final Menu menu = new Menu(); menu.setName(name); diff --git a/src/test/java/kitchenpos/products/application/FakePurgomalumClient.java b/src/test/java/kitchenpos/products/application/FakeCheckBadWordClient.java similarity index 65% rename from src/test/java/kitchenpos/products/application/FakePurgomalumClient.java rename to src/test/java/kitchenpos/products/application/FakeCheckBadWordClient.java index 3c4114798..0f166fd75 100644 --- a/src/test/java/kitchenpos/products/application/FakePurgomalumClient.java +++ b/src/test/java/kitchenpos/products/application/FakeCheckBadWordClient.java @@ -1,11 +1,11 @@ package kitchenpos.products.application; -import kitchenpos.products.infra.PurgomalumClient; +import kitchenpos.products.infra.CheckBadWordClient; import java.util.Arrays; import java.util.List; -public class FakePurgomalumClient implements PurgomalumClient { +public class FakeCheckBadWordClient implements CheckBadWordClient { private static final List profanities; static { @@ -15,6 +15,6 @@ public class FakePurgomalumClient implements PurgomalumClient { @Override public boolean containsProfanity(final String text) { return profanities.stream() - .anyMatch(profanity -> text.contains(profanity)); + .anyMatch(text::contains); } } diff --git a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java deleted file mode 100644 index b55c5ec5e..000000000 --- a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryProductRepository implements ProductRepository { - private final Map products = new HashMap<>(); - - @Override - public Product save(final Product product) { - products.put(product.getId(), product); - return product; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(products.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(products.values()); - } - - @Override - public List findAllByIdIn(final List ids) { - return products.values() - .stream() - .filter(product -> ids.contains(product.getId())) - .toList(); - } -} diff --git a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.kt b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.kt new file mode 100644 index 000000000..4f7949ee2 --- /dev/null +++ b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.kt @@ -0,0 +1,26 @@ +package kitchenpos.products.application + +import kitchenpos.products.domain.Product +import kitchenpos.products.domain.ProductRepository +import java.util.* + +class InMemoryProductRepository : ProductRepository { + private val products: MutableMap = mutableMapOf() + + override fun save(product: Product): Product { + products[product.id] = product + return product + } + + override fun findById(id: UUID?): Optional { + return Optional.ofNullable(products[id]) + } + + override fun findAll(): List { + return products.values.toList() + } + + override fun findAllByIdIn(ids: MutableList?): MutableList { + return products.filterKeys { ids?.contains(it) ?: false }.values.toMutableList() + } +} diff --git a/src/test/java/kitchenpos/products/application/ProductServiceTest.java b/src/test/java/kitchenpos/products/application/ProductServiceTest.java index 74a31073e..f884fa44f 100644 --- a/src/test/java/kitchenpos/products/application/ProductServiceTest.java +++ b/src/test/java/kitchenpos/products/application/ProductServiceTest.java @@ -1,16 +1,21 @@ package kitchenpos.products.application; +import kitchenpos.menus.application.InMemoryMenuGroupRepository; import kitchenpos.menus.application.InMemoryMenuRepository; +import kitchenpos.menus.application.MenuService; import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuRepository; +import kitchenpos.menus.domain.MenuProduct; 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 kitchenpos.products.ui.request.ChangePriceRequest; +import kitchenpos.products.ui.request.CreateProductRequest; +import kitchenpos.products.ui.response.ProductResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import java.math.BigDecimal; @@ -26,76 +31,80 @@ class ProductServiceTest { private ProductRepository productRepository; - private MenuRepository menuRepository; - private PurgomalumClient purgomalumClient; private ProductService productService; + private InMemoryMenuRepository menuRepository; @BeforeEach void setUp() { productRepository = new InMemoryProductRepository(); + CheckBadWordClient checkBadWordClient = new FakeCheckBadWordClient(); menuRepository = new InMemoryMenuRepository(); - purgomalumClient = new FakePurgomalumClient(); - productService = new ProductService(productRepository, menuRepository, purgomalumClient); + MenuService menuService = new MenuService( + menuRepository, + new InMemoryMenuGroupRepository(), + productRepository, + checkBadWordClient + ); + productService = new ProductService(productRepository, checkBadWordClient, menuService); } @DisplayName("상품을 등록할 수 있다.") @Test void create() { - final Product expected = createProductRequest("후라이드", 16_000L); - final Product actual = productService.create(expected); + final CreateProductRequest expected = createProductRequest("후라이드", 16_000L); + final ProductResponse actual = productService.create(expected); assertThat(actual).isNotNull(); assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()), - () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()) + () -> assertThat(actual.getId()).isNotNull(), + () -> assertThat(actual.getName()).isEqualTo(expected.getName()), + () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()) ); } @DisplayName("상품의 가격이 올바르지 않으면 등록할 수 없다.") @ValueSource(strings = "-1000") - @NullSource @ParameterizedTest void create(final BigDecimal price) { - final Product expected = createProductRequest("후라이드", price); + final CreateProductRequest expected = createProductRequest("후라이드", price); assertThatThrownBy(() -> productService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("상품의 이름이 올바르지 않으면 등록할 수 없다.") @ValueSource(strings = {"비속어", "욕설이 포함된 이름"}) - @NullSource @ParameterizedTest void create(final String name) { - final Product expected = createProductRequest(name, 16_000L); + final CreateProductRequest expected = createProductRequest(name, 16_000L); assertThatThrownBy(() -> productService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("상품의 가격을 변경할 수 있다.") @Test void changePrice() { final UUID productId = productRepository.save(product("후라이드", 16_000L)).getId(); - final Product expected = changePriceRequest(15_000L); - final Product actual = productService.changePrice(productId, expected); + final ChangePriceRequest expected = changePriceRequest(15_000L); + final ProductResponse actual = productService.changePrice(productId, expected); assertThat(actual.getPrice()).isEqualTo(expected.getPrice()); } @DisplayName("상품의 가격이 올바르지 않으면 변경할 수 없다.") @ValueSource(strings = "-1000") - @NullSource @ParameterizedTest void changePrice(final BigDecimal price) { final UUID productId = productRepository.save(product("후라이드", 16_000L)).getId(); - final Product expected = changePriceRequest(price); + final ChangePriceRequest expected = changePriceRequest(price); assertThatThrownBy(() -> productService.changePrice(productId, expected)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @DisplayName("상품의 가격이 변경될 때 메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 크면 메뉴가 숨겨진다.") @Test void changePriceInMenu() { final Product product = productRepository.save(product("후라이드", 16_000L)); - final Menu menu = menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))); + // TODO, Change to use Product instead of ProductRecord + final MenuProduct menuProduct = menuProduct(new ProductRecord(product.getId(), product.getName(), product.getPrice()), 2L); + final Menu menu = menuRepository.save(menu(19_000L, true, menuProduct)); productService.changePrice(product.getId(), changePriceRequest(8_000L)); assertThat(menuRepository.findById(menu.getId()).get().isDisplayed()).isFalse(); } @@ -105,28 +114,23 @@ void changePriceInMenu() { void findAll() { productRepository.save(product("후라이드", 16_000L)); productRepository.save(product("양념치킨", 16_000L)); - final List actual = productService.findAll(); + final List actual = productService.findAll(); assertThat(actual).hasSize(2); } - private Product createProductRequest(final String name, final long price) { + private CreateProductRequest createProductRequest(final String name, final long price) { return createProductRequest(name, BigDecimal.valueOf(price)); } - private Product createProductRequest(final String name, final BigDecimal price) { - final Product product = new Product(); - product.setName(name); - product.setPrice(price); - return product; + private CreateProductRequest createProductRequest(final String name, final BigDecimal price) { + return new CreateProductRequest(name, price); } - private Product changePriceRequest(final long price) { + private ChangePriceRequest changePriceRequest(final long price) { return changePriceRequest(BigDecimal.valueOf(price)); } - private Product changePriceRequest(final BigDecimal price) { - final Product product = new Product(); - product.setPrice(price); - return product; + private ChangePriceRequest changePriceRequest(final BigDecimal price) { + return new ChangePriceRequest(price); } }