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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kitchenpos.products.tobe.domain;

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

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

public interface ProductRepositoryImpl {
public Product save(final Product product);

Optional<Product> findById(UUID productId);

void update(Product changedProduct);

List<Product> findAll();
}
45 changes: 45 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/ProductService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kitchenpos.products.tobe.domain;

import kitchenpos.products.tobe.domain.entity.Product;
import kitchenpos.products.tobe.domain.vo.DisplayedName;
import kitchenpos.products.tobe.domain.vo.Price;

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

public class ProductService {
private final ProductRepositoryImpl productRepository;

public ProductService(ProductRepositoryImpl repository) {
this.productRepository = repository;
}


public Product register(DisplayedName name, Price price) {
Product product = new Product(name, price);
product.register(this.productRepository);
return product;
}

public void changeName(UUID productId, String name) throws Exception {
Optional<Product> optionalProduct = this.productRepository.findById(productId);
if (optionalProduct.isEmpty()) {
// Handle the case when the product is not found
throw new Exception("Product not found with id: " + productId);
}

Product product = optionalProduct.get();
Product changedProduct = new Product(productId, new DisplayedName(name), product.getPrice());

this.productRepository.update(changedProduct);
Comment on lines +32 to +35
Copy link

Choose a reason for hiding this comment

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

가격 변경에 대한 로직이 현재 Product 가 아닌 ProductService 에서 수행되고 있는데요!
Product 가 가격변경에 대한 행위를 해보는 것은 어떨까요?

anemic domain model 로 주로 미션해주신 것같아서 교육 목적을 위해 rich domain model 도 경험해보시면 좋을것같아요!

Copy link
Author

Choose a reason for hiding this comment

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

즉 가격 변경에 대한 도메인 검증은 entity 에서 하고 결과를 서비스가 저장한다, 이정도로 구분하면 될까요?

Copy link

Choose a reason for hiding this comment

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

animic domain model 과 rich domain model 에 대해 한번 찾아보시면 좋을 것 같아요!
각각의 장단점을 확인해보시면 좋을 것 같아요!

}

public List<Product> getList() {
return this.productRepository.findAll();
}

public Optional<Product> findById(UUID id) {
return this.productRepository.findById(id);
}
}
68 changes: 68 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/entity/Menu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package kitchenpos.products.tobe.domain.entity;

import kitchenpos.products.tobe.domain.ProductRepositoryImpl;
import kitchenpos.products.tobe.domain.vo.DisplayedName;
import kitchenpos.products.tobe.domain.vo.Price;

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

public class Menu {
private final UUID id;
private final DisplayedName displayedName;
private final List<UUID> productIds;
private final Price price;
private boolean isDisplay;
private final ProductRepositoryImpl repo;


public Menu(UUID id, DisplayedName displayedName, Price price, ProductRepositoryImpl repo) {
this.id = (id != null) ? id : UUID.randomUUID();
this.displayedName = displayedName;
this.price = price;
this.productIds = new ArrayList<>();
this.isDisplay = true;
this.repo = repo;
}

public Menu(DisplayedName displayedName, Price price, ProductRepositoryImpl repo) {
this(UUID.randomUUID(), displayedName, price, repo);
}

public UUID getId() {
return this.id;
}

public boolean isShow() {
return this.isDisplay;
}

public int getProductsTotalPrice() {
int price = 0;
for (UUID productId : this.productIds) {
Optional<Product> product = this.repo.findById(productId);
if (product.isPresent()) {
price += product.get().getPrice().getValue();
}
}
return price;
}

public DisplayedName getName() {
return this.displayedName;
}

public void registerProduct(Product product) {
this.productIds.add(product.getId());
this.compareProductsTotalPriceWithMenuPrice();
}

private void compareProductsTotalPriceWithMenuPrice() {
int totalPrice = this.getProductsTotalPrice();
if (totalPrice > this.price.getValue()) {
this.isDisplay = false;
}
}
}
42 changes: 42 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/entity/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kitchenpos.products.tobe.domain.entity;

import kitchenpos.products.tobe.domain.ProductRepositoryImpl;
import kitchenpos.products.tobe.domain.vo.DisplayedName;
import kitchenpos.products.tobe.domain.vo.Price;

import java.util.UUID;


public class Product {
private final UUID id;
private final DisplayedName displayedName;
private final Price price;

public Product(UUID id, DisplayedName displayedName, Price price) {
this.id = (id != null) ? id : UUID.randomUUID();
this.displayedName = displayedName;
this.price = price;
}

public Product(DisplayedName displayedName, Price price) {
this(UUID.randomUUID(), displayedName, price);
}

public UUID getId() {
return this.id;
}

public Price getPrice() {
return this.price;
}

public DisplayedName getName() {
return this.displayedName;
}


public void register(ProductRepositoryImpl repo) {
Product product = new Product(this.getId(), this.displayedName, this.price);
repo.save(product);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package kitchenpos.products.tobe.domain.infra;

import kitchenpos.products.tobe.domain.ProductRepositoryImpl;
import kitchenpos.products.tobe.domain.entity.Product;

import java.util.*;

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

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

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

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

public List<Product> findAllByIdIn(final List<UUID> ids) {
return products.values()
.stream()
.filter(product -> ids.contains(product.getId()))
.toList();
}

public void update(final Product newProduct) {
Optional<Product> pd = this.findById(newProduct.getId());
List<Product> pds = this.findAll();
if(pd.isEmpty()){
throw new NoSuchElementException("Product not found with id: " + newProduct.getId());
}

this.save(newProduct);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kitchenpos.products.tobe.domain.vo;

import java.util.ArrayList;
import java.util.List;

public class DisplayedName {
private final String value;
public DisplayedName(String value){
this.validate(value);
this.value = value;
}

public String value(){
return this.value;
}

private void validate(String word) {
List<String> wrongWords = new ArrayList<>();
wrongWords.add("wrong-name");

if (wrongWords.contains(word)) {
throw new IllegalArgumentException("The word '" + word + "' is not allowed.");
}
Comment on lines +18 to +23
Copy link

Choose a reason for hiding this comment

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

현재 비속어 검증하는 Profanity 대신 직접 비속어를 List 로 관리하도록 구현해주신것같은데요!
Profatity 대신 직접 구현해주신 의도가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

아 이거 외부 서비스 대체한다 생각하고 그냥 임시코드로 구현한 것이긴 했는데
여기도 repository 처럼 인터페이스를 활용한 DIP 형태로 구현하면 되겠죠?

Copy link

Choose a reason for hiding this comment

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

네네 기존 레거시에 인터페이스와 구현체 모두 있어요! 그것을 활용해보시면 좋을 것 같아요!

}

}
21 changes: 21 additions & 0 deletions src/main/java/kitchenpos/products/tobe/domain/vo/Price.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kitchenpos.products.tobe.domain.vo;

public class Price {
final int value;

public Price(int value) {
this.validate(value);
this.value = value;
}

public int getValue(){
return this.value;
}

private void validate(int value) {
if (value <= 0) {
throw new IllegalArgumentException("Price should be over zero, not negative");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kitchenpos.products.application.tobe;

import kitchenpos.products.tobe.domain.vo.DisplayedName;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class DisplayedNameTest {
@DisplayName("상품의 이름에는 비속어가 포함될 수 없다")
@Test
void setWrongNameTest() throws Exception {
assertThatThrownBy(()->new DisplayedName("wrong-name"))
.isInstanceOf(IllegalArgumentException.class);
}
}
43 changes: 43 additions & 0 deletions src/test/java/kitchenpos/products/application/tobe/MenuTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package kitchenpos.products.application.tobe;

import kitchenpos.products.tobe.domain.entity.Menu;
import kitchenpos.products.tobe.domain.entity.Product;
import kitchenpos.products.tobe.domain.infra.InMemoryProductRepository;
import kitchenpos.products.tobe.domain.vo.DisplayedName;
import kitchenpos.products.tobe.domain.vo.Price;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class MenuTest {
@DisplayName("상품 가격 변동 후 메뉴 가격보다 높으면 메뉴는 숨겨진다")
@Test
void overProductTotalPriceTest() throws Exception {
InMemoryProductRepository repo = new InMemoryProductRepository();
Menu menu = new Menu(new DisplayedName("test"), new Price(5000), repo);
assertThat(menu.isShow()).isEqualTo(true);

int expectedPrice = 6000;
Product pd = new Product(new DisplayedName("100"), new Price(expectedPrice));
pd.register(repo);

menu.registerProduct(pd);

assertThat(menu.isShow()).isEqualTo(false);
}

@DisplayName("Menu 가격을 가져 올 수 있다")
@Test
void getProductsTotalPriceTest() throws Exception {
InMemoryProductRepository repo = new InMemoryProductRepository();
int expectedPrice = 1000;
Product pd = new Product(new DisplayedName("100"), new Price(expectedPrice));
pd.register(repo);

Menu menu = new Menu(new DisplayedName("test"), new Price(5000), repo);
menu.registerProduct(pd);

assertThat(menu.getProductsTotalPrice()).isEqualTo(expectedPrice);
}
}
16 changes: 16 additions & 0 deletions src/test/java/kitchenpos/products/application/tobe/PriceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kitchenpos.products.application.tobe;

import kitchenpos.products.tobe.domain.vo.Price;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class PriceTest {
@DisplayName("가격은 0원 이하가 될 수 없다")
@Test
void setWrongValueTest() throws Exception {
assertThatThrownBy(()->new Price(0))
.isInstanceOf(IllegalArgumentException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kitchenpos.products.application.tobe;

import kitchenpos.products.tobe.domain.infra.InMemoryProductRepository;
import kitchenpos.products.tobe.domain.entity.Product;
import kitchenpos.products.tobe.domain.ProductService;
import kitchenpos.products.tobe.domain.vo.DisplayedName;
import kitchenpos.products.tobe.domain.vo.Price;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

public class ProductServiceTest {
@DisplayName("상품의 이름을 변경할 수 있다")
@Test
void changeGetNameTest() throws Exception {
Price price = new Price(10);
DisplayedName displayedName = new DisplayedName("name");

ProductService service = new ProductService(new InMemoryProductRepository());
Product pd = service.register(displayedName, price);

service.changeName(pd.getId(), "new-name");

Optional<Product> updatedPd = service.findById(pd.getId());
Product upd = updatedPd.get();
assertThat(upd.getName().value()).isEqualTo("new-name");
}

@DisplayName("상품을 등록할 수 있다")
@Test
void registerNewTest() {
Price price = new Price(10);
DisplayedName displayedName = new DisplayedName("name");

ProductService service = new ProductService(new InMemoryProductRepository());
service.register(displayedName, price);

List<Product> pdList = service.getList();
assertThat(pdList).hasSize(1);
}
}
Loading