| name | design-ddd |
| description | DDD 도메인 모델링 판단 기준.
`/design` command의 D step에서 도메인 복잡도가 높을 때 참조.
|
Design DDD — 도메인 모델링 판단 기준
/design command의 D step에서 도메인이 복잡할 때 참조하는 판단 기준.
사용 방법:
/design command의 D step에서 자동 참조됨
- 단독 참조도 가능 (도메인 모델 리뷰 시)
DDD 딥다이브 진입 조건
아래 중 2개 이상 해당하면 DDD 딥다이브가 필요하다:
- 상태 전이가 있다 (예: Draft → Active → Expired)
- 비즈니스 규칙이 3개 이상이다
- 여러 엔티티 간 불변식이 존재한다
- "~할 수 없다" 형태의 제약이 많다
설계 Phase 개요
Phase 1: 용어와 규칙을 모은다 (비즈니스 이해)
↓
Phase 2: 의미가 달라지는 경계를 긋는다 (Bounded Context)
↓
Phase 3: 경계 안에서 구체적으로 설계한다
- 함께 변해야 하는 것을 묶고 (Aggregate)
- 대표를 세우고 (Aggregate Root)
- ID가 필요한 것(Entity)과 값인 것(VO)을 구분하고
- 깨지면 안 되는 규칙을 정하고 (Invariant)
- Aggregate 간 통신 방법을 정한다 (Domain Event)
Phase 1: 용어와 규칙 (비즈니스 이해)
1-1. 유비쿼터스 언어 (Ubiquitous Language)
"도메인 전문가와 개발자가 같은 단어를 같은 뜻으로 쓴다"
도출 방법:
- 요구사항에서 핵심 명사(개념)와 핵심 동사(행위)를 뽑는다
- 용어집 테이블로 정리한다
| 용어 | 정의 | 예시 |
|---|
| {명사} | {한 문장 정의} | {구체적 예} |
| {동사} | {어떤 상태를 어떻게 바꾸는가} | {구체적 예} |
검증 질문:
- 이해관계자가 실제로 쓰는 단어인가?
- 같은 것을 다른 이름으로 부르고 있지 않은가? (동의어 → 하나로 통일)
- 하나의 단어가 문맥에 따라 다른 의미를 가지지 않는가? (동음이의어 → 분리)
동음이의어 발견 시:
- 각각에 고유한 이름을 부여한다
- 이 구분이 Phase 2 Bounded Context 분리의 단서가 된다
1-2. 비즈니스 규칙과 제약
요구사항에서 규칙을 수집한다.
발견 방법:
- "~할 수 없다", "~이어야 한다", "~인 경우에만" 형태를 찾는다
- 명시된 규칙과 암묵적 규칙 모두 포함
Phase 2: Bounded Context (경계 긋기)
"같은 단어라도 맥락에 따라 의미가 달라지는 경계"
경계 판단 기준
Q: 같은 용어가 서로 다른 의미/관심사로 쓰이는 곳이 있는가?
├─ Yes → 별도 Bounded Context
└─ No → 같은 Bounded Context
예시: "상품(Product)"
- 카탈로그 Context: 이름, 설명, 이미지, 카테고리
- 주문 Context: 가격, 재고, 배송 무게
- 리뷰 Context: 평점, 리뷰 수
같은 "상품"이지만 각 맥락에서 관심 있는 속성과 행위가 다르다.
Context 간 관계
| 관계 | 설명 | 예시 |
|---|
| 직접 설계 | 우리가 모델링하는 영역 | 쿠폰 Context |
| 외부 시스템 | ID + 최소 정보만 참조 | 주문 Context (orderId만) |
판단 질문:
- 우리가 직접 설계할 영역은?
- 외부 시스템으로 취급할 영역은?
- 외부에서 필요한 최소 정보는 무엇인가?
Phase 3: 경계 안 설계 (전술적 설계)
3-1. Aggregate 경계
"하나의 트랜잭션으로 일관성을 보장해야 하는 단위"
판단 기준:
Q: A를 변경할 때 B도 반드시 함께 변경되어야 하는가?
├─ Yes → 같은 Aggregate
└─ No → 다른 Aggregate (이벤트로 연결)
Aggregate Root:
- Aggregate의 대표. 외부에서는 Root를 통해서만 내부에 접근한다.
- Root가 내부 요소의 일관성을 보장하는 책임을 진다.
Aggregate가 너무 클 때:
- 서로 다른 생명주기를 가진 부분이 있나?
- 서로 다른 변경 빈도를 가진 부분이 있나?
- 분리해도 불변식을 보장할 수 있는가?
Aggregate가 너무 작을 때:
- 불변식을 보장하기 위해 여러 Aggregate를 동시에 변경해야 하는가?
- 그렇다면 하나로 합쳐야 한다
3-2. Entity vs Value Object
| 기준 | Entity | Value Object |
|---|
| 식별자 | 고유 ID 필요 | 값 자체로 비교 |
| 가변성 | 시간에 따라 상태 변함 | 불변 (immutable) |
| 동등성 | ID로 비교 | 모든 필드로 비교 |
| 예시 | User, Order, Coupon | Money, Address, DateRange |
결정 트리:
Q: 이 개념에 고유 식별자가 필요한가?
├─ Yes → Q: 시간에 따라 상태가 변하는가?
│ ├─ Yes → Entity
│ └─ No → 정말 ID가 필요한지 재검토
└─ No → Q: 같은 값이면 같은 것으로 취급해도 되는가?
├─ Yes → Value Object
└─ No → Entity (식별이 필요한 것)
Value Object로 만들 수 있으면 Value Object가 낫다. 이유:
- 불변이므로 부작용 없음
- 테스트가 쉬움
- 공유해도 안전
3-3. 불변식 (Invariant)
"Aggregate가 항상 보장해야 하는 조건"
형식: {Aggregate}의 {조건}은 항상 {보장}이어야 한다
발견 방법:
- Phase 1에서 수집한 비즈니스 규칙에서 "~할 수 없다", "~이어야 한다" 찾기
- 각 불변식이 어느 Aggregate의 책임인지 할당
- 불변식이 Aggregate 경계를 넘으면 → 경계 재검토
검증:
- 이 불변식을 Aggregate Root의 메서드 안에서 보장할 수 있는가?
- 외부 정보 없이 Aggregate 내부만으로 판단할 수 있는가?
3-4. 책임 할당
"정보 전문가 원칙: 그 판단에 필요한 정보를 누가 갖고 있는가?"
판단 흐름:
Q: 이 행위에 필요한 정보를 누가 갖고 있는가?
├─ 단일 Entity/VO가 가짐 → 그 Entity/VO의 메서드
├─ Aggregate Root가 관리 → Aggregate Root의 메서드
├─ 여러 Aggregate에 걸침 → Domain Service
└─ 외부 시스템 정보 필요 → Application Service (Port를 통해)
3-5. Domain Event
"Aggregate 간 비동기 통신 수단"
필요한 경우:
- Aggregate A의 행위 성공 → Aggregate B가 반응해야 할 때
- 같은 트랜잭션에 있을 필요 없는 후속 처리
이벤트 설계 기준:
- 이름: 과거형 동사 (Created, Updated, Deleted)
- 페이로드: 수신자가 필요한 최소 정보만
- 수신자가 발신자의 내부 구조를 알 필요 없어야 함
흔한 실수
| 실수 | 문제 | 해결 |
|---|
| Phase 2 건너뛰기 | 경계 없이 모든 것이 한 덩어리 | 동음이의어가 있으면 Context 분리 |
| 모든 것을 Entity로 | VO가 적합한 것까지 ID 부여 | "정말 ID가 필요한가?" |
| Aggregate가 하나 | God Object | 변경 빈도와 생명주기로 분리 |
| 불변식 누락 | 비즈니스 규칙 위반 허용 | 규칙마다 "누가 보장?" 질문 |
| Domain Event에 너무 많은 정보 | 결합도 증가 | 수신자 기준 최소 정보 |
| 외부 의존을 Domain에 | Dependency Rule 위반 | Port interface로 역전 |