diff --git a/src/main/java/kitchenpos/global/exception/GlobalRestControllerAdvice.java b/src/main/java/kitchenpos/global/exception/GlobalRestControllerAdvice.java new file mode 100644 index 000000000..d2915e4c7 --- /dev/null +++ b/src/main/java/kitchenpos/global/exception/GlobalRestControllerAdvice.java @@ -0,0 +1,23 @@ +package kitchenpos.global.exception; + +import kitchenpos.global.exception.dto.ErrorCode; +import kitchenpos.global.exception.dto.ExceptionResponse; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.NoSuchElementException; + +@RestControllerAdvice +public class GlobalRestControllerAdvice { + + @ResponseBody + @ExceptionHandler(NoSuchElementException.class) + public ExceptionResponse noSuchElementExceptionHandler(NoSuchElementException exception) { + return new ExceptionResponse( + ErrorCode.NO_SUCH_ELEMENT.getCode(), + ErrorCode.NO_SUCH_ELEMENT.getMessage() + ); + } + +} diff --git a/src/main/java/kitchenpos/global/exception/custom/NotFoundProductException.java b/src/main/java/kitchenpos/global/exception/custom/NotFoundProductException.java new file mode 100644 index 000000000..58f28229e --- /dev/null +++ b/src/main/java/kitchenpos/global/exception/custom/NotFoundProductException.java @@ -0,0 +1,8 @@ +package kitchenpos.global.exception.custom; + +import java.util.NoSuchElementException; + +public class NotFoundProductException extends NoSuchElementException { + public NotFoundProductException() { + } +} diff --git a/src/main/java/kitchenpos/global/exception/dto/ErrorCode.java b/src/main/java/kitchenpos/global/exception/dto/ErrorCode.java new file mode 100644 index 000000000..5106554e8 --- /dev/null +++ b/src/main/java/kitchenpos/global/exception/dto/ErrorCode.java @@ -0,0 +1,22 @@ +package kitchenpos.global.exception.dto; + +public enum ErrorCode { + + NO_SUCH_ELEMENT("E001", "데이터를 찾지 못했습니다."); + + private final String code; + private final String message; + + ErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/global/exception/dto/ExceptionResponse.java b/src/main/java/kitchenpos/global/exception/dto/ExceptionResponse.java new file mode 100644 index 000000000..f046e1a7c --- /dev/null +++ b/src/main/java/kitchenpos/global/exception/dto/ExceptionResponse.java @@ -0,0 +1,20 @@ +package kitchenpos.global.exception.dto; + +public class ExceptionResponse { + + private final String code; + private final String message; + + public ExceptionResponse(String code, String message) { + this.code = code; + this.message = message; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/menus/application/MenuEventListener.java b/src/main/java/kitchenpos/menus/application/MenuEventListener.java new file mode 100644 index 000000000..0cd6df794 --- /dev/null +++ b/src/main/java/kitchenpos/menus/application/MenuEventListener.java @@ -0,0 +1,20 @@ +package kitchenpos.menus.application; + +import kitchenpos.menus.application.dto.MenuEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class MenuEventListener { + + private final MenuService menuService; + + public MenuEventListener(MenuService menuService) { + this.menuService = menuService; + } + + @EventListener + public void changeMenuDisplayStatus(final MenuEvent menuEvent) { + menuService.changeMenuDisplayStatus(menuEvent.getProductId()); + } +} diff --git a/src/main/java/kitchenpos/menus/application/MenuService.java b/src/main/java/kitchenpos/menus/application/MenuService.java index abf913c11..8ba9df8b4 100644 --- a/src/main/java/kitchenpos/menus/application/MenuService.java +++ b/src/main/java/kitchenpos/menus/application/MenuService.java @@ -1,8 +1,8 @@ package kitchenpos.menus.application; import kitchenpos.menus.domain.*; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductRepository; import kitchenpos.products.infra.PurgomalumClient; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,6 +12,7 @@ import java.util.stream.Collectors; @Service +@Transactional public class MenuService { private final MenuRepository menuRepository; private final MenuGroupRepository menuGroupRepository; @@ -30,7 +31,6 @@ public MenuService( this.purgomalumClient = purgomalumClient; } - @Transactional public Menu create(final Menu request) { final BigDecimal price = request.getPrice(); if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { @@ -61,6 +61,7 @@ public Menu create(final Menu request) { .orElseThrow(NoSuchElementException::new); sum = sum.add( product.getPrice() + .getProductPrice() .multiply(BigDecimal.valueOf(quantity)) ); final MenuProduct menuProduct = new MenuProduct(); @@ -85,7 +86,6 @@ public Menu create(final Menu request) { 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) { @@ -98,6 +98,7 @@ public Menu changePrice(final UUID menuId, final Menu request) { sum = sum.add( menuProduct.getProduct() .getPrice() + .getProductPrice() .multiply(BigDecimal.valueOf(menuProduct.getQuantity())) ); } @@ -108,7 +109,6 @@ public Menu changePrice(final UUID menuId, final Menu request) { return menu; } - @Transactional public Menu display(final UUID menuId) { final Menu menu = menuRepository.findById(menuId) .orElseThrow(NoSuchElementException::new); @@ -117,6 +117,7 @@ public Menu display(final UUID menuId) { sum = sum.add( menuProduct.getProduct() .getPrice() + .getProductPrice() .multiply(BigDecimal.valueOf(menuProduct.getQuantity())) ); } @@ -127,7 +128,6 @@ public Menu display(final UUID menuId) { return menu; } - @Transactional public Menu hide(final UUID menuId) { final Menu menu = menuRepository.findById(menuId) .orElseThrow(NoSuchElementException::new); @@ -135,6 +135,19 @@ public Menu hide(final UUID menuId) { return menu; } + public void changeMenuDisplayStatus(final UUID productId) { + menuRepository.findAllByProductId(productId) + .forEach(menu -> { + BigDecimal sum = menu.getMenuProducts().stream() + .map(menuProduct -> menuProduct.getProduct().getPrice().getProductPrice().multiply(BigDecimal.valueOf(menuProduct.getQuantity()))) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + if (menu.getPrice().compareTo(sum) > 0) { + menu.setDisplayed(false); + } + }); + } + @Transactional(readOnly = true) public List findAll() { return menuRepository.findAll(); diff --git a/src/main/java/kitchenpos/menus/application/dto/MenuEvent.java b/src/main/java/kitchenpos/menus/application/dto/MenuEvent.java new file mode 100644 index 000000000..307415b92 --- /dev/null +++ b/src/main/java/kitchenpos/menus/application/dto/MenuEvent.java @@ -0,0 +1,9 @@ +package kitchenpos.menus.application.dto; + +import java.util.UUID; + +public abstract class MenuEvent { + + public abstract UUID getProductId(); + +} diff --git a/src/main/java/kitchenpos/menus/domain/MenuProduct.java b/src/main/java/kitchenpos/menus/domain/MenuProduct.java index 2836096e6..276955c22 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuProduct.java +++ b/src/main/java/kitchenpos/menus/domain/MenuProduct.java @@ -1,6 +1,6 @@ package kitchenpos.menus.domain; -import kitchenpos.products.domain.Product; +import kitchenpos.products.tobe.domain.Product; import javax.persistence.*; import java.util.UUID; diff --git a/src/main/java/kitchenpos/products/application/ProductService.java b/src/main/java/kitchenpos/products/application/ProductService.java deleted file mode 100644 index 20cf63996..000000000 --- a/src/main/java/kitchenpos/products/application/ProductService.java +++ /dev/null @@ -1,81 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; -import kitchenpos.products.infra.PurgomalumClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.UUID; - -@Service -public class ProductService { - private final ProductRepository productRepository; - private final MenuRepository menuRepository; - private final PurgomalumClient purgomalumClient; - - public ProductService( - final ProductRepository productRepository, - final MenuRepository menuRepository, - final PurgomalumClient purgomalumClient - ) { - this.productRepository = productRepository; - this.menuRepository = menuRepository; - this.purgomalumClient = purgomalumClient; - } - - @Transactional - public Product create(final Product request) { - final BigDecimal price = request.getPrice(); - if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); - } - 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); - } - - @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(); - } - 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; - } - - @Transactional(readOnly = true) - public List findAll() { - return productRepository.findAll(); - } -} diff --git a/src/main/java/kitchenpos/products/domain/Product.java b/src/main/java/kitchenpos/products/domain/Product.java deleted file mode 100644 index 456864e20..000000000 --- a/src/main/java/kitchenpos/products/domain/Product.java +++ /dev/null @@ -1,49 +0,0 @@ -package kitchenpos.products.domain; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import java.math.BigDecimal; -import java.util.UUID; - -@Table(name = "product") -@Entity -public class Product { - @Column(name = "id", columnDefinition = "binary(16)") - @Id - private UUID id; - - @Column(name = "name", nullable = false) - private String name; - - @Column(name = "price", nullable = false) - private BigDecimal price; - - public Product() { - } - - public UUID getId() { - return id; - } - - public void setId(final UUID id) { - this.id = 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/tobe/application/ProductService.java b/src/main/java/kitchenpos/products/tobe/application/ProductService.java new file mode 100644 index 000000000..c62286e9b --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/application/ProductService.java @@ -0,0 +1,73 @@ +package kitchenpos.products.tobe.application; + +import kitchenpos.global.exception.custom.NotFoundProductException; +import kitchenpos.menus.domain.MenuRepository; +import kitchenpos.products.tobe.application.dto.ProductEvent; +import kitchenpos.products.tobe.application.dto.ProductInfo; +import kitchenpos.products.tobe.domain.*; +import kitchenpos.products.infra.PurgomalumClient; +import kitchenpos.products.ui.request.ProductChangeRequest; +import kitchenpos.products.ui.request.ProductCreateRequest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@Transactional +public class ProductService { + private final ProductRepository productRepository; + private final PurgomalumClient purgomalumClient; + + private final ApplicationEventPublisher applicationEventPublisher; + + public ProductService( + final ProductRepository productRepository, + final PurgomalumClient purgomalumClient, + final ApplicationEventPublisher applicationEventPublisher + ) { + this.productRepository = productRepository; + this.purgomalumClient = purgomalumClient; + this.applicationEventPublisher = applicationEventPublisher; + } + + public ProductInfo create(final ProductCreateRequest request) { + ProductValidator productValidator = new ProductValidator(request.getName(), request.getPrice(), purgomalumClient); + + final Product product = productRepository.save(new Product( + UUID.randomUUID(), + new ProductName(productValidator.getNameValidator().getName()), + new ProductPrice(productValidator.getPriceValidator().getPrice()) + )); + + return new ProductInfo( + product.getId(), + product.getName().getProductName(), + product.getPrice().getProductPrice() + ); + } + + public ProductInfo changePrice(final UUID productId, final ProductChangeRequest request) { + final Product product = productRepository.findById(productId) + .orElseThrow(NotFoundProductException::new); + PriceValidator priceValidator = new PriceValidator(request.getPrice()); + product.changePrice(new ProductPrice(priceValidator.getPrice())); + + applicationEventPublisher.publishEvent(new ProductEvent(productId)); + + return new ProductInfo( + product.getId(), + product.getName().getProductName(), + product.getPrice().getProductPrice() + ); + } + + @Transactional(readOnly = true) + public List findAll() { + return productRepository.findAll(); + } +} diff --git a/src/main/java/kitchenpos/products/tobe/application/dto/ProductEvent.java b/src/main/java/kitchenpos/products/tobe/application/dto/ProductEvent.java new file mode 100644 index 000000000..8756701b8 --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/application/dto/ProductEvent.java @@ -0,0 +1,19 @@ +package kitchenpos.products.tobe.application.dto; + +import kitchenpos.menus.application.dto.MenuEvent; + +import java.util.UUID; + +public class ProductEvent extends MenuEvent { + + private final UUID productId; + + public ProductEvent(final UUID productId) { + this.productId = productId; + } + + @Override + public UUID getProductId() { + return productId; + } +} diff --git a/src/main/java/kitchenpos/products/tobe/application/dto/ProductInfo.java b/src/main/java/kitchenpos/products/tobe/application/dto/ProductInfo.java new file mode 100644 index 000000000..e53c4316d --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/application/dto/ProductInfo.java @@ -0,0 +1,29 @@ +package kitchenpos.products.tobe.application.dto; + +import java.math.BigDecimal; +import java.util.UUID; + +public class ProductInfo { + + private final UUID id; + private final String name; + private final BigDecimal price; + + public ProductInfo(UUID id, String name, BigDecimal price) { + this.id = id; + this.name = name; + this.price = price; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/products/tobe/domain/NameValidator.java b/src/main/java/kitchenpos/products/tobe/domain/NameValidator.java new file mode 100644 index 000000000..24b84fb95 --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/NameValidator.java @@ -0,0 +1,29 @@ +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.infra.PurgomalumClient; + +import java.util.Objects; + +public class NameValidator { + + private final String name; + + public NameValidator(String name, PurgomalumClient purgomalumClient) { + validateProductName(name, purgomalumClient); + this.name = name; + } + + public String getName() { + return name; + } + + private void validateProductName(final String productName, final PurgomalumClient purgomalumClient) { + if (Objects.isNull(productName)) { + throw new IllegalArgumentException("상품 이름 값이 존재하지 않습니다."); + } + if (purgomalumClient.containsProfanity(productName)) { + throw new IllegalArgumentException("상품 이름에 비속어가 존재합니다."); + } + } + +} diff --git a/src/main/java/kitchenpos/products/tobe/domain/PriceValidator.java b/src/main/java/kitchenpos/products/tobe/domain/PriceValidator.java new file mode 100644 index 000000000..fbe6f6e39 --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/PriceValidator.java @@ -0,0 +1,29 @@ +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.infra.PurgomalumClient; + +import java.math.BigDecimal; +import java.util.Objects; + +public class PriceValidator { + + private final BigDecimal price; + + public PriceValidator(BigDecimal price) { + this.validatePrice(price); + this.price = price; + } + + public BigDecimal getPrice() { + return price; + } + + private void validatePrice(final BigDecimal productPrice) { + if (Objects.isNull(productPrice)) { + throw new IllegalArgumentException("상풍 가격은 필수로 존재해야 합니다."); + } + if (productPrice.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("상품 가격은 0원 이상이어야 합니다."); + } + } +} diff --git a/src/main/java/kitchenpos/products/tobe/domain/Product.java b/src/main/java/kitchenpos/products/tobe/domain/Product.java new file mode 100644 index 000000000..44362c45a --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/Product.java @@ -0,0 +1,45 @@ +package kitchenpos.products.tobe.domain; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.util.UUID; + +@Table(name = "product") +@Entity +public class Product { + + @Column(name = "id", columnDefinition = "binary(16)") + @Id + private UUID id; + + @Embedded + private ProductName name; + + @Embedded + private ProductPrice price; + + protected Product() { + } + + public Product(UUID id, ProductName name, ProductPrice price) { + this.id = id; + this.name = name; + this.price = price; + } + + public UUID getId() { + return id; + } + + public ProductName getName() { + return name; + } + + public ProductPrice getPrice() { + return price; + } + + public void changePrice(final ProductPrice price) { + this.price = price; + } +} diff --git a/src/main/java/kitchenpos/products/tobe/domain/ProductName.java b/src/main/java/kitchenpos/products/tobe/domain/ProductName.java new file mode 100644 index 000000000..a1aaec23a --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/ProductName.java @@ -0,0 +1,38 @@ +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.infra.PurgomalumClient; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class ProductName { + + @Column(name = "name", nullable = false) + private String productName; + + public ProductName(String productName) { + this.productName = productName; + } + + protected ProductName() { + } + + public String getProductName() { + return productName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductName that = (ProductName) o; + return Objects.equals(productName, that.productName); + } + + @Override + public int hashCode() { + return Objects.hash(productName); + } +} diff --git a/src/main/java/kitchenpos/products/tobe/domain/ProductPrice.java b/src/main/java/kitchenpos/products/tobe/domain/ProductPrice.java new file mode 100644 index 000000000..63219f4a4 --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/ProductPrice.java @@ -0,0 +1,37 @@ +package kitchenpos.products.tobe.domain; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.math.BigDecimal; +import java.util.Objects; + +@Embeddable +public class ProductPrice { + + @Column(name = "price", nullable = false) + private BigDecimal productPrice; + + public ProductPrice(BigDecimal productPrice) { + this.productPrice = productPrice; + } + + protected ProductPrice() { + } + + public BigDecimal getProductPrice() { + return productPrice; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductPrice price = (ProductPrice) o; + return Objects.equals(productPrice, price.productPrice); + } + + @Override + public int hashCode() { + return Objects.hash(productPrice); + } +} diff --git a/src/main/java/kitchenpos/products/domain/ProductRepository.java b/src/main/java/kitchenpos/products/tobe/domain/ProductRepository.java similarity index 75% rename from src/main/java/kitchenpos/products/domain/ProductRepository.java rename to src/main/java/kitchenpos/products/tobe/domain/ProductRepository.java index 3637e4232..7a14e4b36 100644 --- a/src/main/java/kitchenpos/products/domain/ProductRepository.java +++ b/src/main/java/kitchenpos/products/tobe/domain/ProductRepository.java @@ -1,4 +1,6 @@ -package kitchenpos.products.domain; +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.tobe.domain.Product; import java.util.List; import java.util.Optional; diff --git a/src/main/java/kitchenpos/products/tobe/domain/ProductValidator.java b/src/main/java/kitchenpos/products/tobe/domain/ProductValidator.java new file mode 100644 index 000000000..5f6bf7f20 --- /dev/null +++ b/src/main/java/kitchenpos/products/tobe/domain/ProductValidator.java @@ -0,0 +1,24 @@ +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.infra.PurgomalumClient; + +import java.math.BigDecimal; +import java.util.Objects; + +public class ProductValidator { + private final NameValidator nameValidator; + private final PriceValidator priceValidator; + + public ProductValidator(String name, BigDecimal price, PurgomalumClient purgomalumClient) { + this.nameValidator = new NameValidator(name, purgomalumClient); + this.priceValidator = new PriceValidator(price); + } + + public NameValidator getNameValidator() { + return nameValidator; + } + + public PriceValidator getPriceValidator() { + return priceValidator; + } +} diff --git a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java b/src/main/java/kitchenpos/products/tobe/infra/JpaProductRepository.java similarity index 56% rename from src/main/java/kitchenpos/products/domain/JpaProductRepository.java rename to src/main/java/kitchenpos/products/tobe/infra/JpaProductRepository.java index 90b069779..4643ae94a 100644 --- a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java +++ b/src/main/java/kitchenpos/products/tobe/infra/JpaProductRepository.java @@ -1,5 +1,7 @@ -package kitchenpos.products.domain; +package kitchenpos.products.tobe.infra; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductRepository; import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; diff --git a/src/main/java/kitchenpos/products/ui/ProductRestController.java b/src/main/java/kitchenpos/products/ui/ProductRestController.java index 7dc532ec2..f400a7556 100644 --- a/src/main/java/kitchenpos/products/ui/ProductRestController.java +++ b/src/main/java/kitchenpos/products/ui/ProductRestController.java @@ -1,13 +1,19 @@ package kitchenpos.products.ui; -import kitchenpos.products.application.ProductService; -import kitchenpos.products.domain.Product; +import kitchenpos.products.tobe.application.ProductService; +import kitchenpos.products.tobe.application.dto.ProductInfo; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.ui.request.ProductChangeRequest; +import kitchenpos.products.ui.request.ProductCreateRequest; +import kitchenpos.products.ui.response.ProductChangeResponse; +import kitchenpos.products.ui.response.ProductCreateResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @RequestMapping("/api/products") @RestController @@ -19,19 +25,42 @@ public ProductRestController(final ProductService productService) { } @PostMapping - public ResponseEntity create(@RequestBody final Product request) { - final Product response = productService.create(request); - return ResponseEntity.created(URI.create("/api/products/" + response.getId())) - .body(response); + public ResponseEntity create(@RequestBody final ProductCreateRequest request) { + final ProductInfo product = productService.create(request); + + return ResponseEntity.created(URI.create("/api/products/" + product.getId())) + .body(new ProductCreateResponse( + product.getId(), + product.getName(), + product.getPrice() + ) + ); } @PutMapping("/{productId}/price") - public ResponseEntity changePrice(@PathVariable final UUID productId, @RequestBody final Product request) { - return ResponseEntity.ok(productService.changePrice(productId, request)); + public ResponseEntity changePrice(@PathVariable final UUID productId, @RequestBody final ProductChangeRequest request) { + final ProductInfo productInfo = productService.changePrice(productId, request); + + return ResponseEntity.ok( + new ProductChangeResponse( + productInfo.getId(), + productInfo.getName(), + productInfo.getPrice() + ) + ); } @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(productService.findAll()); + public ResponseEntity> findAll() { + final List product = productService.findAll(); + + return ResponseEntity.ok( + product.stream() + .map(it -> new ProductInfo( + it.getId(), + it.getName().getProductName(), + it.getPrice().getProductPrice())) + .collect(Collectors.toList()) + ); } } diff --git a/src/main/java/kitchenpos/products/ui/request/ProductChangeRequest.java b/src/main/java/kitchenpos/products/ui/request/ProductChangeRequest.java new file mode 100644 index 000000000..c9dd8d64c --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/request/ProductChangeRequest.java @@ -0,0 +1,31 @@ +package kitchenpos.products.ui.request; + +import java.math.BigDecimal; + +public class ProductChangeRequest { + private String name; + private BigDecimal price; + + private ProductChangeRequest() {} + + public ProductChangeRequest(String name, BigDecimal price) { + this.name = name; + this.price = price; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } +} diff --git a/src/main/java/kitchenpos/products/ui/request/ProductCreateRequest.java b/src/main/java/kitchenpos/products/ui/request/ProductCreateRequest.java new file mode 100644 index 000000000..c752c995d --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/request/ProductCreateRequest.java @@ -0,0 +1,32 @@ +package kitchenpos.products.ui.request; + +import java.math.BigDecimal; +import java.util.UUID; + +public class ProductCreateRequest { + private String name; + private BigDecimal price; + + private ProductCreateRequest() { } + + public ProductCreateRequest(final String name, final BigDecimal price) { + this.name = name; + this.price = price; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } +} diff --git a/src/main/java/kitchenpos/products/ui/response/ProductChangeResponse.java b/src/main/java/kitchenpos/products/ui/response/ProductChangeResponse.java new file mode 100644 index 000000000..963a2ac88 --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/response/ProductChangeResponse.java @@ -0,0 +1,28 @@ +package kitchenpos.products.ui.response; + +import java.math.BigDecimal; +import java.util.UUID; + +public class ProductChangeResponse { + private final UUID id; + private final String name; + private final BigDecimal price; + + public ProductChangeResponse(final UUID id, final String name, final BigDecimal price) { + this.id = id; + this.name = name; + this.price = price; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/products/ui/response/ProductCreateResponse.java b/src/main/java/kitchenpos/products/ui/response/ProductCreateResponse.java new file mode 100644 index 000000000..66ab26f55 --- /dev/null +++ b/src/main/java/kitchenpos/products/ui/response/ProductCreateResponse.java @@ -0,0 +1,29 @@ +package kitchenpos.products.ui.response; + +import java.math.BigDecimal; +import java.util.UUID; + +public class ProductCreateResponse { + + private final UUID id; + private final String name; + private final BigDecimal price; + + public ProductCreateResponse(final UUID id, final String name, final BigDecimal price) { + this.id = id; + this.name = name; + this.price = price; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/test/java/kitchenpos/Fixtures.java b/src/test/java/kitchenpos/Fixtures.java index a17a54ab8..cfa26366c 100644 --- a/src/test/java/kitchenpos/Fixtures.java +++ b/src/test/java/kitchenpos/Fixtures.java @@ -4,7 +4,9 @@ import kitchenpos.menus.domain.Menu; import kitchenpos.menus.domain.MenuGroup; import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.products.domain.Product; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductName; +import kitchenpos.products.tobe.domain.ProductPrice; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -118,10 +120,10 @@ public static Product product() { } 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 new Product( + UUID.randomUUID(), + new ProductName(name), + new ProductPrice(BigDecimal.valueOf(price)) + ); } } diff --git a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java b/src/test/java/kitchenpos/menus/application/MenuServiceTest.java index 23f260848..9503ecc3b 100644 --- a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java +++ b/src/test/java/kitchenpos/menus/application/MenuServiceTest.java @@ -6,8 +6,8 @@ import kitchenpos.menus.domain.MenuRepository; import kitchenpos.products.application.FakePurgomalumClient; import kitchenpos.products.application.InMemoryProductRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductRepository; import kitchenpos.products.infra.PurgomalumClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/kitchenpos/products/application/FakeApplicationEventPublisher.java b/src/test/java/kitchenpos/products/application/FakeApplicationEventPublisher.java new file mode 100644 index 000000000..e2aef0e77 --- /dev/null +++ b/src/test/java/kitchenpos/products/application/FakeApplicationEventPublisher.java @@ -0,0 +1,22 @@ +package kitchenpos.products.application; + +import kitchenpos.menus.application.MenuService; +import kitchenpos.menus.application.dto.MenuEvent; +import kitchenpos.menus.domain.MenuRepository; +import kitchenpos.products.tobe.domain.ProductRepository; +import org.springframework.context.ApplicationEventPublisher; + +public class FakeApplicationEventPublisher implements ApplicationEventPublisher { + private final MenuService menuService; + + public FakeApplicationEventPublisher(ProductRepository productRepository, MenuRepository menuRepository) { + this.menuService = new MenuService(menuRepository, null, productRepository, null); + } + + @Override + public void publishEvent(Object event) { + if (event instanceof MenuEvent) { + menuService.changeMenuDisplayStatus(((MenuEvent) event).getProductId()); + } + } +} diff --git a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java index 2fd78e96d..d4c93ca22 100644 --- a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java +++ b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java @@ -1,7 +1,7 @@ package kitchenpos.products.application; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductRepository; import java.util.*; import java.util.stream.Collectors; diff --git a/src/test/java/kitchenpos/products/application/ProductServiceTest.java b/src/test/java/kitchenpos/products/application/ProductServiceTest.java index 30cc6fb7a..5f35ba8b8 100644 --- a/src/test/java/kitchenpos/products/application/ProductServiceTest.java +++ b/src/test/java/kitchenpos/products/application/ProductServiceTest.java @@ -1,17 +1,25 @@ package kitchenpos.products.application; import kitchenpos.menus.application.InMemoryMenuRepository; +import kitchenpos.menus.application.MenuEventListener; import kitchenpos.menus.domain.Menu; import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; +import kitchenpos.products.tobe.application.ProductService; +import kitchenpos.products.tobe.application.dto.ProductInfo; +import kitchenpos.products.tobe.domain.Product; +import kitchenpos.products.tobe.domain.ProductName; +import kitchenpos.products.tobe.domain.ProductPrice; +import kitchenpos.products.tobe.domain.ProductRepository; import kitchenpos.products.infra.PurgomalumClient; +import kitchenpos.products.ui.request.ProductChangeRequest; +import kitchenpos.products.ui.request.ProductCreateRequest; 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 org.springframework.context.ApplicationEventPublisher; import java.math.BigDecimal; import java.util.List; @@ -27,20 +35,23 @@ class ProductServiceTest { private MenuRepository menuRepository; private PurgomalumClient purgomalumClient; private ProductService productService; + private ApplicationEventPublisher applicationEventPublisher; @BeforeEach void setUp() { productRepository = new InMemoryProductRepository(); menuRepository = new InMemoryMenuRepository(); purgomalumClient = new FakePurgomalumClient(); - productService = new ProductService(productRepository, menuRepository, purgomalumClient); + applicationEventPublisher = new FakeApplicationEventPublisher(productRepository, menuRepository); + + productService = new ProductService(productRepository, purgomalumClient, applicationEventPublisher); } @DisplayName("상품을 등록할 수 있다.") @Test void create() { - final Product expected = createProductRequest("후라이드", 16_000L); - final Product actual = productService.create(expected); + final ProductCreateRequest expected = new ProductCreateRequest("후라이드", new BigDecimal(16000)); + final ProductInfo actual = productService.create(expected); assertThat(actual).isNotNull(); assertAll( () -> assertThat(actual.getId()).isNotNull(), @@ -54,7 +65,7 @@ void create() { @NullSource @ParameterizedTest void create(final BigDecimal price) { - final Product expected = createProductRequest("후라이드", price); + final ProductCreateRequest expected = new ProductCreateRequest("후라이드", price); assertThatThrownBy(() -> productService.create(expected)) .isInstanceOf(IllegalArgumentException.class); } @@ -64,7 +75,7 @@ void create(final BigDecimal price) { @NullSource @ParameterizedTest void create(final String name) { - final Product expected = createProductRequest(name, 16_000L); + final ProductCreateRequest expected = new ProductCreateRequest(name, new BigDecimal(16000)); assertThatThrownBy(() -> productService.create(expected)) .isInstanceOf(IllegalArgumentException.class); } @@ -72,9 +83,10 @@ void create(final String name) { @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 Product product = productRepository.save(product("후라이드", 16_000L)); + final ProductChangeRequest expected = new ProductChangeRequest("", new BigDecimal(15000)); + + final ProductInfo actual = productService.changePrice(product.getId(), expected); assertThat(actual.getPrice()).isEqualTo(expected.getPrice()); } @@ -83,9 +95,9 @@ void changePrice() { @NullSource @ParameterizedTest void changePrice(final BigDecimal price) { - final UUID productId = productRepository.save(product("후라이드", 16_000L)).getId(); - final Product expected = changePriceRequest(price); - assertThatThrownBy(() -> productService.changePrice(productId, expected)) + final Product product = productRepository.save(product("후라이드", 16_000L)); + final ProductChangeRequest expected = new ProductChangeRequest("", price); + assertThatThrownBy(() -> productService.changePrice(product.getId(), expected)) .isInstanceOf(IllegalArgumentException.class); } @@ -94,7 +106,8 @@ void changePrice(final BigDecimal price) { void changePriceInMenu() { final Product product = productRepository.save(product("후라이드", 16_000L)); final Menu menu = menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))); - productService.changePrice(product.getId(), changePriceRequest(8_000L)); + final ProductChangeRequest request = new ProductChangeRequest("", new BigDecimal(8000)); + productService.changePrice(product.getId(), request); assertThat(menuRepository.findById(menu.getId()).get().isDisplayed()).isFalse(); } @@ -112,10 +125,11 @@ private Product createProductRequest(final String name, final long price) { } private Product createProductRequest(final String name, final BigDecimal price) { - final Product product = new Product(); - product.setName(name); - product.setPrice(price); - return product; + return new Product( + UUID.randomUUID(), + new ProductName(name), + new ProductPrice(price) + ); } private Product changePriceRequest(final long price) { @@ -123,8 +137,10 @@ private Product changePriceRequest(final long price) { } private Product changePriceRequest(final BigDecimal price) { - final Product product = new Product(); - product.setPrice(price); - return product; + return new Product( + UUID.randomUUID(), + new ProductName(""), + new ProductPrice(price) + ); } } diff --git a/src/test/java/kitchenpos/products/tobe/domain/NameValidatorTest.java b/src/test/java/kitchenpos/products/tobe/domain/NameValidatorTest.java new file mode 100644 index 000000000..fd34a834c --- /dev/null +++ b/src/test/java/kitchenpos/products/tobe/domain/NameValidatorTest.java @@ -0,0 +1,42 @@ +package kitchenpos.products.tobe.domain; + +import kitchenpos.products.application.FakePurgomalumClient; +import kitchenpos.products.infra.PurgomalumClient; +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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class NameValidatorTest { + + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + this.purgomalumClient = new FakePurgomalumClient(); + } + + + @DisplayName("상품 이름이 존재하지 않으면 에러를 반환한다.") + @ParameterizedTest + @NullSource + void emptyProductNameException(String name) { + assertThatThrownBy(() -> new NameValidator(name, purgomalumClient)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("상품 이름 값이 존재하지 않습니다."); + } + + @DisplayName("상품 이름에 비속어가 존재하면 에러를 반환한다.") + @Test + void containsPurgomalumException() { + final String name = "비속어"; + + assertThatThrownBy(() -> new NameValidator(name, purgomalumClient)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("상품 이름에 비속어가 존재합니다."); + } + +} diff --git a/src/test/java/kitchenpos/products/tobe/domain/PriceValidatorTest.java b/src/test/java/kitchenpos/products/tobe/domain/PriceValidatorTest.java new file mode 100644 index 000000000..87b42563a --- /dev/null +++ b/src/test/java/kitchenpos/products/tobe/domain/PriceValidatorTest.java @@ -0,0 +1,34 @@ +package kitchenpos.products.tobe.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigDecimal; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class PriceValidatorTest { + + @DisplayName("상품 가격이 없으면 에러를 반환한다.") + @NullSource + @ParameterizedTest + void emptyProductPriceException(BigDecimal price) { + assertThatThrownBy(() -> new PriceValidator(price)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("상풍 가격은 필수로 존재해야 합니다."); + } + + @DisplayName("상품가격이 0원 이하이면 에러를 반환한다.") + @ParameterizedTest + @ValueSource(strings = "-1000") + void negativePriceException() { + final BigDecimal price = new BigDecimal(-1000); + + assertThatThrownBy(() -> new PriceValidator(price)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("상품 가격은 0원 이상이어야 합니다."); + } + +}