Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2단계 - 리팩터링(메뉴) #169

Open
wants to merge 35 commits into
base: chr0m3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bde7ea3
Price에 multiply() 추가
chr0m3 Oct 22, 2022
86b5760
MenuGroup entity 추가
chr0m3 Oct 22, 2022
4022276
MenuGroupEntity 추가
chr0m3 Oct 22, 2022
8382a9b
MenuGroupEntityConverter 추가
chr0m3 Oct 22, 2022
a1644b4
MenuGroupRepository 추가
chr0m3 Oct 22, 2022
b84cfe6
JpaMenuGroupRepository, JpaMenuGroupDao 추가
chr0m3 Oct 22, 2022
eb121a6
Price.multiply() 테스트 추가
chr0m3 Oct 22, 2022
4e44a8f
Price에 add() 추가
chr0m3 Oct 22, 2022
2c52931
Price가 Comparable을 구현하도록 수정
chr0m3 Oct 22, 2022
0146904
MenuProductQuantity VO 추가
chr0m3 Oct 22, 2022
a968bc6
MenuProduct VO 추가
chr0m3 Oct 23, 2022
7ff9973
MenuGroup VO 추가
chr0m3 Oct 23, 2022
3621adb
Menu entity 추가
chr0m3 Oct 23, 2022
5aab011
MenuDisplayPolicy 추가
chr0m3 Oct 23, 2022
58bd527
MenuRepository 추가
chr0m3 Oct 23, 2022
0701e17
Menu의 isVisible을 displayed로 변경
chr0m3 Oct 23, 2022
bf9bfb1
MenuEntity 추가
chr0m3 Oct 23, 2022
44adb96
MenuGroup VO 삭제
chr0m3 Oct 24, 2022
2d16e74
MenuProductEntity 추가
chr0m3 Oct 24, 2022
218f580
MenuProductEntityConverter 추가
chr0m3 Oct 24, 2022
c346395
누락된 final 추가
chr0m3 Oct 24, 2022
26aa5e7
MenuEntityConverter 추가
chr0m3 Oct 24, 2022
ac684f6
JpaMenuRepository 추가
chr0m3 Oct 24, 2022
748ee71
JpaMenuRepository 리팩터링 - 중복 코드 제거
chr0m3 Oct 24, 2022
96fe49a
메뉴 노출 정책을 위반하는 가격 변경이 불가능하도록 수정
chr0m3 Oct 24, 2022
f795b5c
새로운 도메인 모델을 사용하도록 Menu 컨텍스트 수정
chr0m3 Oct 24, 2022
2c7035e
불필요해진 과거 코드 제거
chr0m3 Oct 24, 2022
a662282
CreateMenuGroupCommand DTO 추가
chr0m3 Oct 24, 2022
a753755
ChangeMenuPriceCommand DTO 추가
chr0m3 Oct 24, 2022
86dc9e6
CreateMenuCommand DTO 추가
chr0m3 Oct 24, 2022
81afbbc
메뉴 노출 정책을 위반하는 메뉴 생성이 불가능하도록 수정
chr0m3 Oct 24, 2022
bd08ccb
메뉴 생성시 정확한 상품 가격을 반영하도록 개선
chr0m3 Oct 24, 2022
71f200a
DB 마이그레이션
chr0m3 Oct 24, 2022
139c6f8
도메인 모델 수정
chr0m3 Oct 24, 2022
1c460a0
Converter 인터페이스 제거
chr0m3 Oct 24, 2022
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
19 changes: 18 additions & 1 deletion src/main/java/kitchenpos/common/price/Price.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* <li>가격은 음수일 수 없다.</li>
* </ul>
*/
public class Price {
public class Price implements Comparable<Price> {

public final BigDecimal value;

Expand All @@ -30,6 +30,23 @@ public Price(double value) {
this(BigDecimal.valueOf(value));
}

public Price add(Price augend) {
return new Price(this.value.add(augend.value));
}

public Price multiply(BigDecimal multiplicand) {
return new Price(this.value.multiply(multiplicand));
}

public Price multiply(long multiplicand) {
return this.multiply(BigDecimal.valueOf(multiplicand));
}

@Override
public int compareTo(Price o) {
return this.value.compareTo(o.value);
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/kitchenpos/menu/tobe/domain/entity/Menu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package kitchenpos.menu.tobe.domain.entity;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import kitchenpos.common.name.Name;
import kitchenpos.common.price.Price;
import kitchenpos.menu.tobe.domain.service.MenuDisplayPolicy;
import kitchenpos.menu.tobe.domain.vo.MenuGroup;
import kitchenpos.menu.tobe.domain.vo.MenuProduct;

public class Menu {

public final UUID id;

public final Name name;

public final MenuGroup menuGroup;

private final List<MenuProduct> menuProducts;

private Boolean displayed;
Copy link
Author

@chr0m3 chr0m3 Oct 24, 2022

Choose a reason for hiding this comment

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

굳이 isVisible로 바꿔야만 하는 이유가 없어서 불필요한 데이터베이스 변경을 만들지 않기 위해 displayed를 그대로 사용하기로 했습니다.


private Price price;

public Menu(
final UUID id,
final Name name,
final Boolean displayed,
final Price price,
final MenuGroup menuGroup,
final List<MenuProduct> menuProducts
) {
this.id = id;
this.name = name;
this.displayed = displayed;
this.price = price;
this.menuGroup = menuGroup;
this.menuProducts = menuProducts;
}

public boolean displayed() {
return this.displayed;
}

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

public List<MenuProduct> menuProducts() {
return Collections.unmodifiableList(this.menuProducts);
}

public void display() {
if (!MenuDisplayPolicy.isDisplayable(this)) {
throw new IllegalStateException("메뉴 노출 정책에 따라 이 메뉴를 노출할 수 없습니다");
}
this.displayed = true;
}

public void hide() {
this.displayed = false;
}

public void setPrice(final Price price) {
Copy link

Choose a reason for hiding this comment

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

setter 네이밍을 사용하기 보다는 의미있는 네이밍을 고민해보세요 😄

if (!MenuDisplayPolicy.isDisplayable(this)) {
this.hide();
}
this.price = price;
}
}
16 changes: 16 additions & 0 deletions src/main/java/kitchenpos/menu/tobe/domain/entity/MenuGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kitchenpos.menu.tobe.domain.entity;

import java.util.UUID;
import kitchenpos.common.name.Name;

public class MenuGroup {

public final UUID id;

public final Name name;

public MenuGroup(final UUID id, final Name name) {
this.id = id;
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kitchenpos.menu.tobe.domain.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import kitchenpos.menu.tobe.domain.entity.MenuGroup;

public interface MenuGroupRepository {

MenuGroup save(MenuGroup menuGroup);

Optional<MenuGroup> findById(UUID id);

List<MenuGroup> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kitchenpos.menu.tobe.domain.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import kitchenpos.menu.tobe.domain.entity.Menu;

public interface MenuRepository {

Menu save(final Menu menu);

Optional<Menu> findById(final UUID id);

List<Menu> findAll();

List<Menu> findAllByIdIn(final List<UUID> ids);

List<Menu> findAllByProductId(final UUID productId);
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kitchenpos.menu.tobe.domain.service;


import kitchenpos.common.price.Price;
import kitchenpos.menu.tobe.domain.entity.Menu;
import kitchenpos.menu.tobe.domain.vo.MenuProduct;
import org.springframework.stereotype.Service;

@Service
public class MenuDisplayPolicy {
Copy link
Author

Choose a reason for hiding this comment

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

이 때는 별도의 클래스로 분리했지만 실제로 Menu aggrigate만을 사용하므로 Menu 내부에 두어도 문제가 없을 것 같다는 생각을 했습니다. 별도의 클래스로 분리하는 것과 내부에 두는 것 서로 장단점이 있는 것 같습니다.

Copy link

Choose a reason for hiding this comment

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

현재 구현해주신 코드는 Menu Aggregate만을 사용하게 되지만, 가격 변경시 메뉴에 속한 상품의 합보다 적거나 같아야한다.와 같은 정책이 지켜지지 않을것 같아요.
메뉴가 가지고 있는 가격이 현시점에 상품 가격이라고 확신할수 없는 구조 입니다.


private MenuDisplayPolicy() {
}

public static boolean isDisplayable(final Menu menu) {
final Price total = menu.menuProducts()
.stream()
.map(MenuProduct::subtotal)
.reduce(new Price(0), Price::add);
return menu.price().compareTo(total) <= 0;
}
}
31 changes: 31 additions & 0 deletions src/main/java/kitchenpos/menu/tobe/domain/vo/MenuGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kitchenpos.menu.tobe.domain.vo;

import kitchenpos.common.name.Name;

public class MenuGroup {

public final Name name;

public MenuGroup(final Name name) {
this.name = name;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

MenuGroup menuGroup = (MenuGroup) o;

return name.equals(menuGroup.name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}
55 changes: 55 additions & 0 deletions src/main/java/kitchenpos/menu/tobe/domain/vo/MenuProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package kitchenpos.menu.tobe.domain.vo;

import java.util.UUID;
import kitchenpos.common.price.Price;

public class MenuProduct {

public final UUID productId;

public final Price pricePerUnit;
Copy link
Author

Choose a reason for hiding this comment

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

단순히 price를 사용하는 경우 메뉴 하나의 가격인지, 메뉴 가격에 수량을 곱한 소계에 해당하는 값인지 혼동되어 명확하게 네이밍했습니다.


public final MenuProductQuantity quantity;

public MenuProduct(
final UUID productId,
final Price pricePerUnit,
final MenuProductQuantity quantity
) {
this.productId = productId;
this.pricePerUnit = pricePerUnit;
this.quantity = quantity;
}

public Price subtotal() {
return this.pricePerUnit.multiply(this.quantity.value);
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

MenuProduct that = (MenuProduct) o;

if (!productId.equals(that.productId)) {
return false;
}
if (!pricePerUnit.equals(that.pricePerUnit)) {
return false;
}
return quantity.equals(that.quantity);
}

@Override
public int hashCode() {
int result = productId.hashCode();
result = 31 * result + pricePerUnit.hashCode();
result = 31 * result + quantity.hashCode();
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kitchenpos.menu.tobe.domain.vo;

/**
* <h1>메뉴 상품 수량</h1>
* <ul>
* <li>메뉴 상품별 수량은 1 이상이다.</li>
* </ul>
*/
public class MenuProductQuantity {

final long value;

public MenuProductQuantity(final long value) {
if (value < 1) {
throw new IllegalArgumentException("MenuProductQuantity는 1보다 작을 수 없습니다");
}
this.value = value;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

MenuProductQuantity that = (MenuProductQuantity) o;

return value == that.value;
}

@Override
public int hashCode() {
return (int) (value ^ (value >>> 32));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kitchenpos.menu.tobe.infra.jpa;

import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface JpaMenuGroupDao extends JpaRepository<MenuGroupEntity, UUID> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package kitchenpos.menu.tobe.infra.jpa;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import kitchenpos.menu.tobe.domain.entity.MenuGroup;
import kitchenpos.menu.tobe.domain.repository.MenuGroupRepository;
import kitchenpos.menu.tobe.infra.jpa.MenuGroupEntityConverter.MenuGroupEntityToMenuGroupConverter;
import kitchenpos.menu.tobe.infra.jpa.MenuGroupEntityConverter.MenuGroupToMenuGroupEntityConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class JpaMenuGroupRepository implements MenuGroupRepository {

private final JpaMenuGroupDao jpaMenuGroupDao;

private final MenuGroupToMenuGroupEntityConverter menuGroupToMenuGroupEntityConverter;

private final MenuGroupEntityToMenuGroupConverter menuGroupEntityToMenuGroupConverter;

@Autowired
public JpaMenuGroupRepository(
JpaMenuGroupDao jpaMenuGroupDao,
MenuGroupToMenuGroupEntityConverter menuGroupToMenuGroupEntityConverter,
MenuGroupEntityToMenuGroupConverter menuGroupEntityToMenuGroupConverter
) {
this.jpaMenuGroupDao = jpaMenuGroupDao;
this.menuGroupToMenuGroupEntityConverter = menuGroupToMenuGroupEntityConverter;
this.menuGroupEntityToMenuGroupConverter = menuGroupEntityToMenuGroupConverter;
}

@Override
public MenuGroup save(MenuGroup menuGroup) {
final MenuGroupEntity menuGroupEntity = this.menuGroupToMenuGroupEntityConverter.convert(menuGroup);
final MenuGroupEntity result = this.jpaMenuGroupDao.save(menuGroupEntity);
return this.menuGroupEntityToMenuGroupConverter.convert(result);
}

@Override
public Optional<MenuGroup> findById(UUID id) {
final Optional<MenuGroupEntity> result = this.jpaMenuGroupDao.findById(id);
if (result.isEmpty()) {
return Optional.empty();
}
return Optional.of(this.menuGroupEntityToMenuGroupConverter.convert(result.get()));
}

@Override
public List<MenuGroup> findAll() {
return this.jpaMenuGroupDao.findAll()
.stream()
.map(this.menuGroupEntityToMenuGroupConverter::convert)
.collect(Collectors.toUnmodifiableList());
}
}
20 changes: 20 additions & 0 deletions src/main/java/kitchenpos/menu/tobe/infra/jpa/MenuGroupEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kitchenpos.menu.tobe.infra.jpa;

import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class MenuGroupEntity {

@Column(name = "id", columnDefinition = "binary(16)")
@Id
public UUID id;

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

public MenuGroupEntity() {
}
}
Loading