핵심 한 줄
DDD는 “무엇을(도메인 모델) 어떻게 나눌까”의 전략, 헥사고날은 “그 도메인을 외부(DB·API·UI)로부터 어떻게 격리할까”의 구조다. 둘은 경쟁 관계가 아니라 DDD가 그린 경계를 헥사고날이 코드로 강제하는 보완 관계다.
이 노트의 성격
핵심 개념을 새로 정리한 노트다. 결제 취소 MSA 설계 경험과 연결해 보강하면 좋다.
1. DDD (Domain-Driven Design)
전략적 설계 (Strategic)
- 유비쿼터스 언어: 도메인 전문가와 개발자가 같은 용어 사용 (코드·대화·문서 일치)
- 바운디드 컨텍스트: 같은 단어도 맥락마다 의미가 다름 → 경계로 분리 (예: “상품”이 주문 맥락 vs 재고 맥락에서 다른 모델)
- 컨텍스트 맵: 컨텍스트 간 관계 정의 (MSA 서비스 경계와 직결 → 결제 취소 MSA의 4개 서비스 분리)
전술적 설계 (Tactical)
| 빌딩블록 | 역할 | 식별 기준 |
|---|---|---|
| Entity | 식별자로 구분, 생명주기 보유 | ID로 동일성 판단 |
| Value Object | 값으로 구분, 불변 | 속성이 같으면 같음 (예: Money, Address) |
| Aggregate | 일관성 경계, 트랜잭션 단위 | Root를 통해서만 접근 |
| Domain Service | 엔티티에 안 맞는 도메인 로직 | 여러 애그리거트에 걸친 규칙 |
| Repository | 애그리거트 저장/조회 추상화 | 영속성 기술 은닉 |
애그리거트 = 트랜잭션 경계
결제 취소 같은 정합성 핵심 도메인에서 애그리거트 단위를 잘 잡으면 동시성 제어·일관성이 자연스럽게 풀린다. 너무 크면 락 경합, 너무 작으면 정합성 깨짐 → 트레이드오프.
2. 헥사고날 아키텍처 (Ports & Adapters)
핵심 발상
도메인 로직은 바깥세상(DB, 웹, 메시지큐)을 몰라야 한다. 의존성은 항상 바깥→안쪽으로만 흐른다.
[웹 컨트롤러]──┐ ┌──[JPA 어댑터]
[gRPC]────────┼─▶ (Inbound Port) (Outbound Port) ◀─┼──[Kafka 어댑터]
[배치]────────┘ │ [도메인 + 유스케이스] │ └──[외부 API 어댑터]
└────────── 코어 ──────────┘
어댑터(주도하는 쪽) 어댑터(주도되는 쪽)
포트 / 어댑터
- Inbound Port (Driving): 유스케이스 인터페이스. 외부가 코어를 호출하는 진입점
- Outbound Port (Driven): 코어가 외부에 요구하는 인터페이스 (예:
PaymentRepository) - Adapter: 포트의 구현체. 기술 의존(JPA, Kafka, REST)은 전부 여기에 격리
의존성 역전 (DIP)
- 코어가
Repository인터페이스를 정의 → JPA 어댑터가 구현 - 결과: DB를 PostgreSQL→다른 것으로 바꿔도 도메인 코드는 불변. 테스트 시 in-memory 어댑터로 교체 가능
3. DDD ↔ 헥사고날 연결
| DDD 개념 | 헥사고날 위치 |
|---|---|
| 애그리거트·엔티티·VO | 코어(도메인) 내부 |
| Domain Service | 코어 내부 |
| 유스케이스(애플리케이션 서비스) | Inbound Port |
| Repository 인터페이스 | Outbound Port |
| Repository 구현(JPA 등) | Adapter |
| 바운디드 컨텍스트 | 헥사곤 1개 = 컨텍스트 1개 (MSA 서비스 경계) |
핵심: DDD는 경계를 설계하고, 헥사고날은 그 경계를 의존성 규칙으로 코드에 강제한다.
4. 실무 트레이드오프 (한계)
- 오버엔지니어링 위험: 단순 CRUD에 헥사고날 풀세트는 보일러플레이트만 늘린다. 도메인 복잡도가 높을 때만 효과
- 매핑 비용: 도메인 모델 ↔ 영속성 엔티티 ↔ DTO 변환 코드 증가
- 러닝커브: 팀 전체가 유비쿼터스 언어·계층 규칙을 공유해야 효과
- 적용 판단: 결제·정산처럼 규칙이 복잡하고 정합성이 중요한 도메인에 우선 적용, 조회 위주 영역은 단순하게
5. 다음 행동
- 결제 취소 MSA를 헥사고날로 재설계한 다이어그램 작성 → 포트폴리오 보강
- 애그리거트 경계와 트랜잭션 경계가 일치했는지 회고
- NestJS/Spring에서 포트-어댑터 디렉터리 구조 예시 코드 정리