Skip to content

🚀 1단계 - 리팩터링(상품)#328

Open
sumiini wants to merge 2 commits intonext-step:sumiinifrom
sumiini:step1
Open

🚀 1단계 - 리팩터링(상품)#328
sumiini wants to merge 2 commits intonext-step:sumiinifrom
sumiini:step1

Conversation

@sumiini
Copy link

@sumiini sumiini commented Feb 15, 2025

안녕하세요 리뷰어님.
products 패키지 하위에 tobe.domain 패키지를 만들었습니다.
JpaProductRepository 구현체 생성하였습니다.
잘 부탁드립니다 🙂

Copy link

@Rok93 Rok93 left a comment

Choose a reason for hiding this comment

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

수민님 안녕하세요. 도메인 주도 설계 기본 요소 미션을 함께하게된 김경록입니다.
1단계 미션 잘 구현해주셨습니다만 현재 애플리케이션이 기동하지 않는 것으로 보이네요. 😢

애플리케이션이 기동되는 상태로 변경 부탁드릴게요.
추가적으로 기존의 API를 리팩터링하신 코드로 대체해보시면 좋을 것 같아요.
또 테스트 코드 또한 기존의 서비스 로직이 아닌 리팩터링한 서비스 로직으로 대체했을 때, 기존의 동작을 동일하게 수행하는지를 확인해주셨으면합니다. 🙏

이 미션은 실제로 서비스중인 코드를 수정한다고 생각하고 최대한 리팩터링을 진행해주시면 좋을 것 같아요. 😃

그 외에도 몇몇 코멘트 남겨두었으니 확인 부탁드릴게요.

import java.util.Objects;
import java.util.UUID;

@Service
Copy link

Choose a reason for hiding this comment

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

현재 애플리케이션 기동이 안되고있는 것 같아요.
다음 리뷰 요청 때는, 애플리케이션이 기동되는 상태가 되도록해주세요. 😃

}

@Transactional
public Product create(final Product request) {
Copy link

Choose a reason for hiding this comment

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

기존의 서비스 로직을 사용하던 위치들을 리팩터링한 로직들을 대신 사용하도록 변경해주세요.

지금의 상태에서는 리팩터링한 코드들이 이전처럼 잘 동작할지에 대한 확신이 들지 않는데요. 🤔
테스트 코드 또한 변경된 ProductService로 대체해주신 뒤, 테스트 코드가 잘 돌아가는지 확인이되면 좋을 것 같아요. 😃

throw new IllegalArgumentException();
}
final Product product = productRepository.findById(productId)
.orElseThrow(NoSuchElementException::new);
Copy link

Choose a reason for hiding this comment

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

예외가 발생했을 때, 적절한 에러 메시지를 던져준다면 에러메시지를 통해서 문제의 원인을 파악하기가 더 수월해지지 않을까요? 😃

Suggested change
.orElseThrow(NoSuchElementException::new);
.orElseThrow(() -> new NoSuchElementException("상품이 존재하지 않습니다. productId: " + productId));

import jakarta.persistence.Embeddable;
import kitchenpos.products.infra.PurgomalumClient;
@Embeddable
public class Name {
Copy link

Choose a reason for hiding this comment

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

README.md에 적힌 용어 사전, 모델링을 봤을 때는 Name이 아닌 DisplayedName으로 정의해야할 것 같은데요. 🤔
혹시 이전 미션 코드 or 문서를 가지고와야하는 상황이라면 해당 PR close 후, 0단계 PR을 올려주세요!

}
final Product product = productRepository.findById(productId)
.orElseThrow(NoSuchElementException::new);
product.setPrice(price);
Copy link

Choose a reason for hiding this comment

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

setter를 이용하기보다는, 변경의 의미를 드러낼 수 있도록 적절히 메서드명을 네이밍해보면 좋을 것 같습니다.

Suggested change
product.setPrice(price);
product.changePrice(price);


@Entity
@Table(name = "product")
public class ProductEntity {
Copy link

Choose a reason for hiding this comment

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

순수 도메인 객체와 JPA의 Entity를 분리하셨군요? 👍

import jakarta.persistence.Embeddable;
import kitchenpos.products.infra.PurgomalumClient;
@Embeddable
public class Name {
Copy link

Choose a reason for hiding this comment

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

Name, Price의 경우 VO라고 볼 수 있지 않을까요? 😃

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.

추후 다른 컨텍스트에서도 Price가 등장할 것 같은데요.
모두 같은 Price를 쓰는걸까요? 그게 아니라면 네이밍적으로 구분을 해두는게 좋지 않을까요? ex) ProductPrice

Comment on lines +36 to +43
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();
}
Copy link

Choose a reason for hiding this comment

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

Price, Name 값 객체를 생성하기만해도 이 검증은 자연스럽게 수행될 것 같은데요. 🤔
두 값 객체를 생성하여 product의 생성자 파라미터를 통해 주입해서 Product 객체를 생성하도록 변경해보시면 좋을 것 같습니다. 😉

@sumiini
Copy link
Author

sumiini commented Feb 18, 2025

안녕하세요 @Rok93 리뷰어님 !
궁금한 사항이 있어 문의드립니다.
기존 사용하던 Product를 순수 도메인 객체로 만들면서
Product, Price(ProductPrice), Name(ProductName)으로 분리 하였습니다.
이때 모든 연관된 Product서비스로직과 테스트 로직 ,Fixture에서 모두 에러 발생해서
어디부터 손을대야할지 고민이 되었습니다. 고민하고 방향을 잡다 시간이 늘어나고, 에러 발생할 때 마다 그냥 고치는 느낌이 들었습니다.

혹시 이런 상황일 때는
로직을 어디서부터 고치거나 수정하는것이 좀 더 효율적일까요?!😥

@Rok93
Copy link

Rok93 commented Mar 1, 2025

@sumiini 님 안녕하세요.
에구... 질문 남겨주신 것을 이제서야 확인했네요. 🥲 (혹시 다음에도 질문을 남겨주셨는데, 답변이 없다면 추가적으로 DM으로도 꼭 남겨주세요. 🙏)

기존 Product에 의존하는 로직들이 많다보니 말씀하신 것처럼 어디서부터 수정해야할지 고민이되실 수도 있을 것 같아요. 🤔

저라면 Product의 내부의 상태 값의 타입은 바뀌더라도, 기존의 동작을 유지하도록 할 것 같아요.

생성자 오버로딩, getter, setter의 반환 타입 혹은 메서드 파라미터를 기존과 동일하게 받되, 내부적으로는 VO 객체로 변환해서 할당하도록 수정한다거나, (물론 추후 setter는 가급적 제거를 하도록 리팩터링하고 싶을 것 같아서, @Deprecated 주석을 추가해두고 추후 리팩터링할 때, 개선해볼 것 같아요)

아마 Fixture함수도 지금은 setter를 통해서 fixture를 생성하고있을 것 같은데 setter가 기존처럼 사용해도 동일하게 동작하도록 호환성만 유지해준다면 당장에 컴파일 에러가 나는 부분은 적을 것 같아요. 🤔

일단 지금처럼 최대한 기존의 로직들이 동작하도록 변경을 가하고 또 다음 단계로 하나씩 수정해나가는 방향으로 점진적인 리팩터링을 해가는 것이 좋다고 생각해요.

정말정말 최후의 수단으로 너무 의존성이 많이 엮여있어 어렵다면 별도의 Product 객체를 만들어서 완전히 새것으로 갈아 엎은 뒤, 변경을 해줄 것 같네요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants