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

[Step3] 3단계 - 기능 우선 패키지 구성하기 #477

Open
wants to merge 5 commits into
base: suzhanlee
Choose a base branch
from

Conversation

suzhanlee
Copy link

@suzhanlee suzhanlee commented Feb 11, 2025

이번 단계 마지막 미션이네요!
이번에는 바운디드 컨텍스트 중심의 패키지 구조로 리팩토링해봤습니다 :)
(step2를 기반으로 최대한 정리해봤습니다 ㅎㅎ)

감사합니다!

Copy link

@mj950425 mj950425 left a comment

Choose a reason for hiding this comment

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

안녕하세요, 수찬님. 리뷰가 늦어진 점 양해 부탁드립니다.
비즈니스 확장을 염두에 두고 미션을 진행해보시면 더 의미 있는 경험이 될 것 같습니다.
또한, 외부 서비스와의 통신 스펙(인터페이스)을 어디에 두는 것이 DIP 원칙에 부합할지 고민해보시면 더욱 좋은 학습이 될 것 같습니다.
관련해서 코멘트 남겨두었는데 확인 부탁드릴게요.

@@ -1,4 +1,4 @@
package kitchenpos.infra;
package kitchenpos.menu.infrastructure.external;

public interface PurgomalumClient {

Choose a reason for hiding this comment

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

현재 통신 스펙이 external 패키지에 존재함으로써, 어플리케이션이 인프라에 의존하고 있는데요.
클린 아키텍처 관점에 맞게 수정해보는것은 어떨까요?

image

@@ -1,4 +1,4 @@
package kitchenpos.domain;

Choose a reason for hiding this comment

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

매장 내 식사 주문, 테이크 아웃 주문, 배달 주문이 한 곳에 뭉쳐있네요.

같은 주문이어도 바운디드 컨택스트(모델의 경계)가 다르다고 생각하는데요.

분리해보는것은 어떨까요?

@@ -1,4 +1,4 @@
package kitchenpos.domain;

Choose a reason for hiding this comment

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

의식적으로 확장에 대한 가능성을 염두하고 미션을 진행해보는것도 좋을 것 같습니다.

그런 관점에서 메뉴랑 상품도 바운디드 컨택스트를 분리해볼 수 있을 것 같아요.

어떻게 생각하시나요?

@suzhanlee
Copy link
Author

suzhanlee commented Feb 13, 2025

안녕하세요 리뷰어님!
패키지 의존성에 대해 공부를 한 후 다시 진행해봤습니다!
진행하다 보니 구현을 생각을 안하기가 힘들어서 sudo 코드 형식으로 진행해봤습니다!

진행한 점

  1. Order 쪽(rider) ~> service(use case) 계층이 infra 에 대해 의존하는 문제 해결 => port, adaptor 사용
  2. Menu 쪽(비속어 검증) ~> port/adaptor 패턴을 사용해야 할 때는 주로 port가 도메인에 강하게 의존해야 할 때라고 생각했습니다! (ex. RiderPort는 OrderId를 가지고 있음)
    이와 달리 PurgomalumClient는 Menu나 Product에 직접적으로 의존하는 부분이 없음! ~> 이를 고려해 다시 인터페이스로 dip 할 필요 없이 service가 infra 가 아닌 domain에 의존할 수 있게 domain 패키지에 PurgomalumClient 를 이동시켰습니다!
제가 1, 2 번에서 생각한게 맞는지 궁금합니다! :)
  1. step2에서 고려한 Order 모델링을 고려해서 아주 간단하게 코드를 작성해봤습니다!
    여기서 기존 Order와 OrderType을 수정하지 않고, CustomOrder와 CustomOrderType을 따로 만들어봤습니다.
    그런데, Order는 Delivery, TakeOut, EatIn 3가지 바운디드 컨텍스트로 나뉠 수 있다고 생각하여 이를 구현하기 위해
    OrderType을 enum이 아닌 별도의 클래스로 가져가기로 했습니다.
    Order를 애그리거트로 생각하고, 주문이라는 하위 도메인에 대해 이를 3가지 문제로 쪼개 해결하는 중,
    OrderType을 인터페이스로 받아 Order entity에 매핑하려 하니 이를 할 수 없다는 것을 알았습니다.
    그래서 우회하는 방식으로 추상클래스를 만들어서 진행해야하는 문제가 발생했습니다 ㅠㅠ
만약 이렇게 Order 를 애그리거트로 생각하고, 3가지 바운디드 컨텍스트를 만들면,
order라는 패키지에 order, delivery, eatin, takeout 이렇게 4개의 하위 패키지를 만드는게 좋을까요?
아니면 order 라는 상위 패키지 없이 가야하는 걸까요?
저는 같은 애그리거트에 속한다면 직접 참조를 통해 연결될테니, 같은 패키지에 두고 싶어서 일단 이렇게 구성해봤습니다!
또한 용어 사전에서도 order 에 공통적인 주문에 대한 용어를 정의하고, 
나머지 3개에 대해 각각 다른 부분에 대해 정의해서 이를 함께 고려해서 적용해봤습니다 :)

이런 경우에는 문제를 어떤 방식으로 해결하는게 좋을까요?
이해를 위해 Delivery 쪽만 아주 간단하게 구현해봤습니다.

항상 도움이 크게 되는 피드백을 주셔서 많이 배우고 있습니다 ㅎㅎ
감사합니다 :)

@2minjoon
Copy link

안녕하세요 수찬님. 고민을 많이 하시는 것 같아서 좋습니다.

1,2번 질문에 대한 답변)

도메인 레이어는 비즈니스 로직의 핵심을 담당하는 영역입니다.

외부 시스템에 대한 의존성을 제거하면, 외부 변경이 발생하더라도 핵심 비즈니스 로직을 수정할 필요가 없어 더 안정적인 구조를 만들 수 있습니다.

예를 들어, 기존처럼 PurgomalumClient를 인프라 레이어에 위치시킨다면, MenuService는 PurgomalumClient의 시그니처가 변경될 때 함께 영향을 받아 컴파일 에러가 발생할 수 있습니다.

이러한 의존성 문제는 도메인 레이어가 인프라 변경에 의해 보호되지 못한다는 의미이며, 이를 컴파일 타임 의존성을 가진다고 표현할 수 있습니다.

제가 의도했던 방향은 PurgomalumClient를 도메인이 아닌 애플리케이션 레이어로 이동하는 것이었습니다.

애플리케이션 레이어에서 이를 관리하면 도메인이 직접 외부 API와 결합되지 않도록 보호할 수 있으며, DIP를 통해 유연한 구조를 유지할 수 있습니다.

여기까지가 DIP 개념인데요.

헥사고날 아키텍처의 포트 어댑터 패턴은 DIP 개념을 확장한 것입니다.

인바운드 요청: 컨트롤러에서 도메인 레이어로 요청이 들어올 때, 도메인 레이어에서 정의한 인터페이스(포트) 규격에 맞춰 요청이 전달됩니다.

아웃바운드 요청: 도메인 레이어에서 외부 시스템으로 요청을 보낼 때도, 도메인이 직접 외부 시스템을 호출하지 않고 포트를 통해 통신하도록 합니다.

인바운드로 요청이 들어올 때도 도메인 레이어에 선언한 인터페이스 규격에 맞춰서 요청을 넣어주고, 아웃바운드로 요청이 나갈때도 도메인 레이어에서 선언한 인터페이스에 맞게 요청이 나가는것을 의미합니다.

아웃바운드 요청의 경우, DIP 개념과 동일하게 인터페이스(포트)를 활용하여 도메인과 외부 시스템의 결합을 제거합니다.

인바운드 요청의 경우에는 애초에 컴파일 타임 의존성이 컨트롤러 → 서비스 방향이므로 DIP를 적용할 필요는 없습니다.

도메인 레이어를 더욱 견고하게 보호하기 위해 애플리케이션 레이어에서 스펙을 명시하고, 컨트롤러가 이를 준수하도록 설계하는 것입니다.

하지만 좀 더 견고하게 도메인 레이어를 지키기 위해서 어플리케이션 레이어에서 스펙을 명시하고 컨트롤러는 이를 준수하도록 권장함으로써 도메인 레이어를 안전하게 지키기 위함입니다.

돌아와서, 질문에서 언급하신 포트와 어댑터 개념은 아웃바운드 요청을 다루는 부분으로 보입니다.

이는 DIP를 적용하기 위한 “인터페이스와 구현체”의 개념과 크게 다르지 않습니다.

즉, 포트는 도메인에 강하게 의존하는 것이 아니라, 오히려 도메인을 외부 변경으로부터 보호하기 위한 장치입니다.

따라서, “포트가 도메인에 강하게 의존해야 할 때 포트/어댑터 패턴을 사용한다”는 표현보다는, “도메인을 보호하기 위해 포트/어댑터 패턴을 사용한다”는 것이 더 적절한 표현입니다.

3번 질문에 대한 답변)
바운디드 컨텍스트는 애그리거트의 하위 개념이 아닙니다.

즉, 배달 주문, 매장 내 식사 주문, 포장 주문은 주문이라는 루트 애그리거트의 하위 개념이 아닙니다.

각각의 주문 유형이 공통적으로 “주문”이라는 속성을 가지고 있지만, 비즈니스적으로는 서로 다른 경계를 가집니다.

이들은 앞으로 확장될 비즈니스 로직이 다를 가능성이 높기 때문에, 별도의 바운디드 컨텍스트로 나누는 것이 더 적절합니다.

제안 드리는 방식은 아래처럼 패키지 구조를 잡거나,

order
├── delivery
├── takeout
├── eatin

또는 아래처럼 잡는게 어떨까요?

delivery_order
takeout_order
eatin_order

또한 주문이라는 애그리거트에 포장 주문, 매장 내 식사 주문, 배달 주문이라는 별도 엔티티를 엮는 것 보다 각각의 엔티티를 만들어보는것은 어떨까요?

한번 고민해보셨으면 좋겠습니다. 이 과정이 큰 도움이 될 것이라고 믿어요.

Copy link

@2minjoon 2minjoon left a comment

Choose a reason for hiding this comment

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

안녕하세요 수찬님. 많은 고민을 하시고 질문 주신 것 같아 아주 좋네요. 질문 주신 내용들에 대해서 제 의견들을 적어봤는데요. 꼭 고민해보시고 의견을 주시면 좋겠습니다. 이 과정이 큰 의미가 있을 것이라고 믿습니다 💪

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.

3 participants