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
73 changes: 73 additions & 0 deletions src/main/java/kitchenpos/menus/tobe/domain/MenuProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package kitchenpos.menus.tobe.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import kitchenpos.products.tobe.domain.Product;

import java.util.UUID;

@Table(name = "menu_product")
@Entity
public class MenuProduct {
@Column(name = "seq")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long seq;

@ManyToOne(optional = false)
@JoinColumn(
name = "product_id",
columnDefinition = "binary(16)",
foreignKey = @ForeignKey(name = "fk_menu_product_to_product")
)
private Product product;

@Column(name = "quantity", nullable = false)
private long quantity;

@Transient
private UUID productId;

public MenuProduct() {
}

public Long getSeq() {
return seq;
}

public void setSeq(final Long seq) {
this.seq = seq;
}

public Product getProduct() {
return product;
}

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

public long getQuantity() {
return quantity;
}

public void setQuantity(final long quantity) {
this.quantity = quantity;
}

public UUID getProductId() {
return productId;
}

public void setProductId(final UUID productId) {
this.productId = productId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package kitchenpos.products.tobe.application;

import kitchenpos.menus.domain.Menu;
import kitchenpos.menus.domain.MenuProduct;
import kitchenpos.menus.domain.MenuRepository;
import kitchenpos.products.tobe.domain.Product;
import kitchenpos.products.tobe.domain.ProductName;
import kitchenpos.products.tobe.domain.ProductRepository;
import kitchenpos.products.infra.PurgomalumClient;
import kitchenpos.products.tobe.domain.Price;
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
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 String name = request.getName().getName();
final Price price = new Price(request.getPrice().getPrice());
final ProductName productName = new ProductName(name, purgomalumClient);
final Product product = new Product(productName, price);
return productRepository.save(product);
}

@Transactional
public Product changePrice(final UUID productId, final Product request) {
final Price price = new Price(request.getPrice().getPrice());
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;
}

@Transactional(readOnly = true)
public List<Product> findAll() {
return productRepository.findAll();
}
}
54 changes: 54 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/Price.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package kitchenpos.products.tobe.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;

import java.math.BigDecimal;
import java.util.Objects;

@Embeddable
public class Price {
Copy link

Choose a reason for hiding this comment

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

👍


@Column(name = "price", nullable = false)
private BigDecimal price;

protected Price() {
}

public Price(Long price) {
if (Objects.isNull(price)) {
throw new IllegalArgumentException();
}
var tempPrice = BigDecimal.valueOf(price);
validate(tempPrice);
this.price = tempPrice;
}

public Price(BigDecimal price) {
validate(price);
this.price = price;
}

public void validate(BigDecimal price) {
if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException();
}
}

public BigDecimal getPrice() {
return price;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Price price1 = (Price) o;
return Objects.equals(price, price1.price);
}

@Override
public int hashCode() {
return Objects.hashCode(price);
}
}
52 changes: 52 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kitchenpos.products.tobe.domain;

import jakarta.persistence.*;

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;
Copy link

Choose a reason for hiding this comment

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

VO 사용 👍


@Embedded
private Price price;

public Product() {
}

public Product(ProductName name, Price price) {
this.id = UUID.randomUUID();
this.name = name;
this.price = price;
}

public UUID getId() {
return id;
}

public void setId(final UUID id) {
Copy link

Choose a reason for hiding this comment

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

Setter를 의미있게 변경해보면 어떨까요?
단순 Setter가 필요없다면 다른 방식으로 풀어보는 것도 좋을 것 같네요~! 😄

Copy link
Author

@wotjd4305 wotjd4305 Mar 2, 2025

Choose a reason for hiding this comment

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

Product의 ID만을 세팅하는 경우는 없고, 생성하기 위해서 이름, 가격을 주입 받고 있습니다!
제거하고 생성자로 생성하는게 나을 것 같아요

this.id = id;
}

public ProductName getName() {
return name;
}

public void setName(final ProductName name) {
this.name = name;
}

public Price getPrice() {
return price;
}

public void setPrice(Price price) {
this.price = price;
}
}
51 changes: 51 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/ProductName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package kitchenpos.products.tobe.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import kitchenpos.products.infra.PurgomalumClient;

import java.math.BigDecimal;
import java.util.Objects;

@Embeddable
public class ProductName {

@Column(name = "name", nullable = false)
private String name;

protected ProductName() {
}

public ProductName(String name, PurgomalumClient purgomalumClient) {
validate(name, purgomalumClient);
this.name = name;
}

public void validate(String name, PurgomalumClient purgomalumClient) {
if (Objects.isNull(name) || name.trim().isEmpty()) {
throw new IllegalArgumentException();
}

if (purgomalumClient.containsProfanity(name)) {
throw new IllegalArgumentException();
}
this.name = name;
}
Comment on lines +19 to +33
Copy link

Choose a reason for hiding this comment

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

ProductName의 역할이 무엇인지 알 수 있겠네요~! 👍


public String getName() {
return name;
}

@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(name, that.name);
}

@Override
public int hashCode() {
return Objects.hashCode(name);
}
Comment on lines +39 to +50
Copy link

Choose a reason for hiding this comment

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

꼭 필요한 선언이죠~! 👍

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kitchenpos.products.tobe.domain;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface ProductRepository {
Product save(Product product);

Optional<Product> findById(UUID id);

List<Product> findAll();

List<Product> findAllByIdIn(List<UUID> ids);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package kitchenpos.products.tobe.application;

import kitchenpos.products.tobe.domain.Product;
import kitchenpos.products.tobe.domain.ProductRepository;

import java.util.*;

public class InMemoryProductRepository implements ProductRepository {
private final Map<UUID, Product> products = new HashMap<>();

@Override
public Product save(final Product product) {
products.put(product.getId(), product);
return product;
}

@Override
public Optional<Product> findById(final UUID id) {
return Optional.ofNullable(products.get(id));
}

@Override
public List<Product> findAll() {
return new ArrayList<>(products.values());
}

@Override
public List<Product> findAllByIdIn(final List<UUID> ids) {
return products.values()
.stream()
.filter(product -> ids.contains(product.getId()))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kitchenpos.products.tobe.application;

import kitchenpos.products.tobe.domain.Price;
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 static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;

class PriceServiceTest {

@DisplayName("가격 생성 성공")
@ParameterizedTest
@ValueSource(longs = {0L, 1L, 1000L, 999999L})
void success(Long price) {
assertThatNoException()
.isThrownBy(() -> new Price(price));
}

@DisplayName("가격은 null이 될 수 없다.")
@ParameterizedTest
@NullSource
void fail_null_price(Long price) {
assertThatIllegalArgumentException().isThrownBy(() -> new Price(price));
}

@DisplayName("가격은 0보다 작을 수 없다.")
@Test
void fail_0_price() {
assertThatIllegalArgumentException().isThrownBy(() -> new Price(-1L));
}
}
Loading