전술적 설계 핵심 요약
Entity → ID로 구분하고, 상태 변화 가능하며, 비즈니스 로직을 캡슐화한다
Value Object → 값으로 구분하고, 불변이며, 자가 검증한다
Aggregate → 일관성 경계를 형성하고, Root를 통해 접근하며, 작게 유지한다
Domain Event → 과거 사실을 나타내고, 느슨한 결합을 이루며, 통합에 활용한다
Repository → Aggregate 단위로 영속성을 추상화한다
Domain Service → 여러 객체에 걸친 무상태 도메인 로직을 다룬다
Factory → 복잡한 객체 생성을 캡슐화한다
App Service → 유스케이스를 조율한다.(도메인 지식이 없다)
도메인 로직은 도메인 객체 안에! Application Service는 조율만!
전술적 설계 태스크 (전략적 설계 완료 후)
전략적 설계(Bounded Context, Context Map, Ubiquitous Language)가 완료된 상태에서
전술적 설계를 진행하는 순서와 각 태스크를 정리.
전체 흐름
[설계 확인]
└─ 전략적 설계 산출물 검토
│
▼
[도메인 모델 설계] ← 코드 작성 전, 팀 합의 단계
├─ Entity / Value Object 분류
├─ Aggregate 경계 설계
└─ 불변식(Invariant) 정의
│
▼
[도메인 계층 구현] ← 인프라 의존 없는 순수 도메인 코드
├─ Value Object 구현 (Entity보다 먼저)
├─ Aggregate / Entity 구현
├─ Domain Event 구현
├─ Domain Service 구현
├─ Factory 구현
└─ Repository 인터페이스 정의
│
▼
[검증 → 외부 연결]
├─ 도메인 단위 테스트
├─ Application Service 구현
├─ Repository 구현체 (인프라)
├─ Domain Event 핸들러 구현
└─ 통합 테스트 및 최종 검증
Task 1. 전략적 설계 산출물 검토 및 전술 설계 준비
완료된 전략적 설계 결과물(Bounded Context 목록, Context Map, Ubiquitous Language 사전)을 검토하고, 전술적 설계를 시작할 BC 우선순위를 결정해야 한다. 각 BC별 핵심 도메인 개념 목록 초안을 작성해야 한다.
체크리스트
- BC 목록과 각 BC의 책임 범위를 파악한다
- Context Map에서 BC 간 관계 유형을 확인한다 (ACL, Shared Kernel, Conformist 등)
- Ubiquitous Language 사전 BC별 정리를 확인한다
- 구현 착수 BC 우선순위를 결정한다
Task 2. BC별 Entity / Value Object 식별 및 분류
Ubiquitous Language 사전을 기반으로 각 BC 내 개념을 Entity와 Value Object로 분류해야 한다.
판단 기준: “식별자(ID)가 필요한가?” → Entity / 값 자체로 구분되는가? → Value Object
| 개념 | 식별자 필요? | 분류 | 이유 |
|---|---|---|---|
| Order | ✅ | Entity | 주문 이력 추적 필요 |
| Customer | ✅ | Entity | 개별 고객 관리 필요 |
| Product | ✅ | Entity | 상품 정보 변경 추적 |
| Money | ❌ | Value Object | 금액+통화 값으로 충분 |
| Address | ❌ | Value Object | 주소 자체가 의미 단위 |
| Quantity | ❌ | Value Object | 수량 값으로 충분 |
체크리스트
- 모든 UL 개념에 대해 분류를 완료한다
- 분류 결과를 팀/도메인 전문가와 검증한다
- 모호한 개념은 UL 사전에 추가로 정의한다
Task 3. Aggregate 경계 설계 및 Aggregate Root 지정
Entity들을 묶어 일관성 경계(Aggregate)를 설계하고 각 Aggregate의 Root를 지정해야 한다.
설계 원칙
- 작게 유지해야 한다 — 성능, 동시성 문제 예방
- 다른 Aggregate는 ID로만 참조한다
- 트랜잭션 경계 = Aggregate 경계다
- Context Map을 참고해서 외부 BC 참조 방식을 결정한다 (ID 참조 vs ACL)
체크리스트
- 각 Aggregate의 Root Entity를 지정한다
- Aggregate 간 참조가 ID로만 이루어지는지 확인한다
- 트랜잭션 경계가 Aggregate 경계와 일치하는지 검토한다
- 외부 BC 참조 방식을 결정한다 (Context Map 기반)
Task 4. Aggregate별 불변식(Invariant) 정의
각 Aggregate가 항상 보장해야 하는 비즈니스 규칙을 도출해야 한다.
불변식은 이후 Entity 메서드 내부의 guard 조건으로 직접 구현된다.
예시
Order Aggregate
- 주문 확정 시 아이템이 1개 이상이어야 한다
- 확정된 주문은 수정할 수 없다
- 취소된 주문은 재확정할 수 없다
Product Aggregate
- 재고가 0 미만이 될 수 없다
- 판매 중지 상품은 주문에 추가할 수 없다
체크리스트
- 각 Aggregate에 대해 불변식 목록을 작성한다
- 도메인 전문가와 불변식을 검증한다
- 불변식이 특정 메서드에서만 적용되는지 / 항상 적용되는지 구분한다
Task 5. Value Object 구현
분류된 Value Object를 코드로 구현해야 한다. Entity 구현 전에 먼저 완성해야 한다.
구현 요건
- 불변이어야 한다 (
data class또는val만 사용) - 생성자(init 블록)에서 유효성 검증을 한다
- 값 기반 동등성을 구현한다(
equals/hashCode) - 변경 시 새 객체를 반환한다
체크리스트
- 모든 Value Object 구현을 완료한다
- 각 VO의 유효성 검사 로직 포함 여부를 확인한다
- 불변성 보장을 확인한다 (mutable 필드 없음)
- VO 단위 테스트를 작성한다
Task 6. Aggregate 및 Entity 구현
Aggregate Root 및 내부 Entity를 구현해야 한다.
구현 요건
- 불변식을 메서드 guard 조건으로 반영해야 한다 (
check,require) - 상태 변경은 의미있는 메서드명으로 표현해야 한다 (
confirm(),cancel()) - 컬렉션을 직접 노출하면 안 되고 내부
MutableList, 외부List반환을 해야 한다 - 생성은
private생성자 +companion objectfactory 패턴을 사용해야 한다
체크리스트
- 모든 Aggregate Root 구현을 완료한다
- 불변식이 메서드 내 guard 조건으로 구현되었는지 확인한다
- setter 직접 노출이 없는지 확인한다
- 다른 Aggregate를 ID로만 참조하는지 확인한다
Task 7. Domain Event 도출 및 구현
각 Aggregate의 주요 상태 변화에서 Domain Event를 도출하고 구현해야 한다.
네이밍 규칙: 과거형 동사 + 명사를 사용한다 (OrderConfirmed, PaymentCompleted)
예시 이벤트 목록
OrderPlaced → 재고 감소, 결제 요청
OrderConfirmed → 배송 준비 시작
OrderCancelled → 재고 복구, 환불 처리
PaymentCompleted → 주문 상태 변경
체크리스트
- 주요 상태 변화마다 이벤트를 도출한다
- 이벤트를 불변
data class로 구현한다 -
occurredAt타임스탬프를 포함한다 - BC 간 통합 이벤트를 별도로 구분한다
- Aggregate Root에
registerEvent()패턴을 적용한다
Task 8. Domain Service 식별 및 구현
특정 Entity/Value Object에 자연스럽게 속하지 않는 도메인 로직을 Domain Service로 분리해야 한다.
판단 기준
“이 로직이 특정 객체의 책임인가?”
YES → Entity/VO의 메서드 / NO → Domain Service
체크리스트
- 여러 Aggregate에 걸치는 로직을 식별한다
- 무상태(stateless) 구현을 확인한다
- 인프라 의존이 없는지 확인한다 (인프라 의존이 생기면 Application Service로 이동)
Task 9. Factory 구현
복잡한 Aggregate 생성 로직을 Factory로 캡슐화해야 한다.
| 방식 | 사용 시기 |
|---|---|
companion object factory | 간단한 생성, 외부 의존 없음 |
| 별도 Factory 클래스 | 외부 의존성 필요 (Repository 등) |
| Factory Method | 생성 책임이 다른 도메인 개념에 있을 때 |
체크리스트
- 생성 로직이 복잡한 Aggregate를 식별한다
- 생성 시 불변식 검증 포함 여부를 확인한다
- 외부 의존성 유무에 따라 방식을 결정한다
Task 10. Repository 인터페이스 정의 (도메인 계층)
Aggregate Root마다 Repository 인터페이스를 도메인 계층에 정의해야 한다.
원칙
- Aggregate Root 하나 당 Repository 하나를 둔다
- 메서드명은 Ubiquitous Language 기반으로 한다
- 인터페이스만 도메인 계층에 위치하고 구현체는 인프라 계층에 둔다
체크리스트
- 모든 Aggregate Root에 대한 Repository 인터페이스를 작성한다
- 조회 메서드가 도메인 언어로 표현되어 있는지 확인한다
- 인터페이스가 인프라에 의존하지 않는지 확인한다
Task 11. 도메인 계층 단위 테스트 작성
인프라 없이 순수 도메인 로직만 테스트해야 한다.
테스트 대상
- Entity 불변식 검증을 한다 (정상 케이스 + 예외 케이스)
- Value Object 동등성 / 연산 / 유효성 검사를 한다
- Domain Service 로직을 테스트한다
- Domain Event 발행 여부를 확인한다
체크리스트
- 각 불변식에 대한 테스트 케이스를 작성한다
- 경계값(boundary) 케이스를 포함한다
- 외부 의존성 없이 테스트 실행이 가능한지 확인한다
Task 12. Application Service 구현
유스케이스 단위로 Application Service를 작성해야 한다.
역할
- 트랜잭션을 관리한다
- 권한/보안을 확인한다
- Repository / Factory / Domain Service 호출을 조율한다
- 도메인 로직은 이 계층에 두지 않는다
체크리스트
- 유스케이스 단위로 Command/Query를 분리한다 (CQRS 고려)
- 트랜잭션 경계가 Aggregate 경계와 일치하는지 확인한다
- 도메인 로직이 Application Service에 새어나오지 않는지 리뷰한다
Task 13. Repository 구현체 및 영속성 매핑 작성 (인프라 계층)
실제 저장소(JPA/MongoDB 등) 구현체와 도메인 ↔ 영속성 모델 매핑을 작성해야 한다.
체크리스트
- 도메인 계층 Repository 인터페이스를 구현한다
- 도메인 객체 ↔ 영속성 모델 Mapper를 구현한다
-
save()시 Domain Event 발행을 연동한다 - DB 교체 시 도메인 코드 변경 없이 가능한지 확인한다
Task 14. Domain Event 핸들러 구현 및 BC 간 통합
이벤트별 핸들러를 구현하고 BC 간 통합 방식을 결정해야 한다.
통합 방식 선택 기준 (Context Map 기반)
- 같은 BC 내 → 동기 처리를 한다
- 다른 BC → 비동기 처리를 한다 (메시지 큐 / Kafka)
- Downstream BC가 ACL 적용 중 → Anti-Corruption Layer 통해 변환한다
체크리스트
- 각 Domain Event에 대한 핸들러를 작성한다
- BC 간 통합 방식을 결정하고 문서화한다
- 이벤트 발행 실패 시 재시도/보상 트랜잭션 전략을 수립한다
Task 15. 통합 테스트 및 전술 설계 검증
Application Service → Domain → Repository → DB 전체 흐름을 통합 테스트하고, 전술 설계가 전략적 설계 산출물을 올바르게 반영하는지 최종 검증해야 한다.
체크리스트
- 주요 유스케이스 E2E 통합 테스트를 작성한다
- BC 간 이벤트 연동 흐름을 테스트한다
- Ubiquitous Language가 코드(클래스명/메서드명)에 반영되어 있는지 리뷰한다
- Aggregate 경계가 Context Map의 BC 경계와 일치하는지 검토한다
- 불변식이 모든 진입점에서 보호되고 있는지 확인한다