DDD 이론 1: DDD 철학과 복잡성
Domain-Driven Design의 탄생 배경, 핵심 철학, 전략적/전술적 설계의 전체 그림을 조망합니다.
Part 1: DDD의 기초
- 1. DDD의 탄생 배경
- 2. 소프트웨어 복잡성의 본질
- 3. 도메인 모델이란 무엇인가
- 4. Knowledge Crunching
Part 2: DDD의 전체 그림
- 5. 전략적 설계 개요
- 6. 전술적 설계 개요
- 7. DDD의 핵심 원칙
- 8. DDD를 적용해야 할 때와 하지 말아야 할 때
- • DDD가 탄생하게 된 배경과 해결하고자 하는 문제를 이해한다
- • 소프트웨어 복잡성의 본질과 DDD의 접근 방식을 파악한다
- • 도메인 모델의 역할과 중요성을 설명할 수 있다
- • Knowledge Crunching 프로세스를 이해하고 적용할 수 있다
- • 전략적 설계와 전술적 설계의 차이와 관계를 이해한다
- • DDD의 핵심 빌딩 블록들을 개괄적으로 파악한다
- • DDD 적용의 적절한 상황을 판단할 수 있다
Part 1: DDD의 기초
DDD가 무엇이고 왜 필요한지 이해합니다
1. DDD의 탄생 배경
"소프트웨어의 복잡성은 기술적인 것이 아니라 도메인 자체에서 온다. 우리가 만드는 소프트웨어의 심장은 도메인에 관련된 문제를 사용자를 위해 해결하는 능력에 있다."
— Eric Evans, Domain-Driven Design (2003)
에릭 에반스는 누구인가?
에릭 에반스(Eric Evans)는 소프트웨어 설계 컨설턴트로, 수십 년간 복잡한 비즈니스 시스템을 설계하고 구축한 경험을 바탕으로 2003년 "Domain-Driven Design: Tackling Complexity in the Heart of Software"(일명 "Blue Book")를 출간했습니다.
그는 성공적인 프로젝트들의 공통점을 분석하면서, 도메인 전문가와 개발자의 긴밀한 협력과비즈니스 개념을 코드에 직접 반영하는 것이 핵심임을 발견했습니다.
2003년, 소프트웨어 개발의 위기
DDD가 등장한 2003년, 소프트웨어 산업은 심각한 문제에 직면해 있었습니다. Standish Group의 CHAOS Report에 따르면 IT 프로젝트의 70% 이상이 실패하거나 심각한 문제를 겪고 있었습니다.
기술 중심 사고
데이터베이스 스키마, 프레임워크, 기술 스택에 집중하며 비즈니스 문제는 뒷전으로 밀림
의사소통 단절
개발자와 비즈니스 전문가가 서로 다른 언어로 대화하여 요구사항이 왜곡됨
빈약한 도메인 모델
비즈니스 로직이 서비스 계층에 흩어지고, 도메인 객체는 데이터 컨테이너로 전락
Big Ball of Mud
시스템이 커질수록 구조 없이 얽히고설켜 변경 비용이 기하급수적으로 증가
"효과적인 모델링의 요소들: 1) 모델과 구현의 연결 2) 모델 기반의 언어 정제 3) 풍부한 지식을 담은 모델 개발 4) 모델의 정제 5) 브레인스토밍과 실험"
— Eric Evans, Domain-Driven Design, Chapter 1
DDD 이후의 발전
에릭 에반스의 Blue Book 이후, DDD는 계속 발전해왔습니다:
Eric Evans - "Domain-Driven Design" (Blue Book)
DDD의 기초 개념과 패턴 정립
Vaughn Vernon - "Implementing DDD" (Red Book)
실용적인 구현 가이드, Aggregate 설계 규칙 정교화
Vaughn Vernon - "Domain-Driven Design Distilled"
DDD 핵심 개념의 간결한 요약
Event Storming, CQRS, Event Sourcing
Alberto Brandolini의 Event Storming, Greg Young의 CQRS/ES
2. 소프트웨어 복잡성의 본질
"소프트웨어 구축에서 가장 어려운 부분은 무엇을 만들지 결정하는 것이다. 기술적 구현보다 요구사항을 이해하고 올바른 모델을 찾는 것이 더 어렵다."
— Fred Brooks, No Silver Bullet (1986)
본질적 복잡성 vs 우발적 복잡성
프레드 브룩스는 "No Silver Bullet"에서 소프트웨어 복잡성을 두 가지로 구분했습니다. DDD는 이 중 본질적 복잡성(Essential Complexity)에 집중합니다.
본질적 복잡성 (Essential)
문제 도메인 자체에서 오는 복잡성. 피할 수 없으며, 이해하고 모델링해야 함.
- • 비즈니스 규칙의 복잡성
- • 도메인 개념 간의 관계
- • 예외 케이스와 엣지 케이스
- • 규제와 컴플라이언스 요구사항
- • 시간에 따른 상태 변화
우발적 복잡성 (Accidental)
기술적 선택이나 잘못된 설계에서 오는 복잡성. 줄이거나 제거할 수 있음.
- • 불필요한 추상화 계층
- • 잘못된 기술 선택
- • 레거시 시스템과의 통합
- • 과도한 프레임워크 의존성
- • 복잡한 배포 환경
"도메인 복잡성을 다루는 가장 중요한 도구는 좋은 도메인 모델이다. 모델은 지식의 선택적 추상화이다."
— Eric Evans, Domain-Driven Design, Chapter 1
복잡성이 증가하는 이유
도메인 지식의 암묵화
비즈니스 규칙이 코드 여기저기에 흩어지고, 왜 그렇게 구현했는지 아무도 모름. 새로운 개발자가 합류하면 같은 실수를 반복.
모델과 코드의 괴리
설계 문서와 실제 코드가 달라지고, 문서는 방치됨. 코드만이 진실이 되지만 그 코드를 이해하기 어려움.
기술 부채의 누적
빠른 구현을 위한 임시 코드가 영구화되고, 변경 비용이 증가. "나중에 고치자"가 "영원히 안 고침"이 됨.
경계 없는 성장
명확한 경계 없이 기능이 추가되면서 모듈 간 의존성이 스파게티화. 한 곳을 수정하면 예상치 못한 곳이 깨짐.
언어의 불일치
비즈니스 팀은 "주문"이라 하고, 개발팀은 "Order 엔티티"라 하고, DB팀은 "ORDER_TBL"이라 함. 번역 과정에서 의미 손실.
복잡성 관리의 핵심 전략
분할 (Divide)
큰 문제를 작은 문제로 나눔. Bounded Context로 경계 설정.
추상화 (Abstract)
핵심만 남기고 세부사항 숨김. 도메인 모델로 본질 표현.
캡슐화 (Encapsulate)
관련된 것을 묶고 내부 숨김. Aggregate로 일관성 보장.
3. 도메인 모델이란 무엇인가
"도메인 모델은 특정 다이어그램이 아니다. 다이어그램이 전달하고자 하는 아이디어다. 그것은 도메인 전문가의 머릿속에 있는 지식이 아니라, 그 지식의 엄격하게 조직된 선택적 추상화다."
— Eric Evans, Domain-Driven Design, Chapter 1
도메인(Domain)이란?
도메인은 소프트웨어가 해결하고자 하는 비즈니스 문제 영역입니다. 사용자가 프로그램을 적용하는 대상 영역이며, 소프트웨어가 존재하는 이유입니다.
🛒 이커머스
상품 판매, 주문, 결제, 배송
🏦 금융
계좌, 거래, 대출, 투자
🏥 의료
환자, 진료, 처방, 보험
🚚 물류
배송, 재고, 창고, 운송
모델(Model)이란?
모델은 현실 세계의 선택적 추상화입니다. 모든 것을 표현하는 것이 아니라, 문제 해결에 필요한 측면만 선택하여 단순화한 것입니다.
의사소통의 도구
개발자와 도메인 전문가가 같은 언어로 대화할 수 있게 함
지식의 저장소
도메인에 대한 이해를 구조화하고 보존함
구현의 기반
코드가 모델을 직접 반영하여 설계와 구현이 일치함
좋은 도메인 모델 vs 나쁜 도메인 모델
✓ 좋은 모델
- • 도메인 전문가가 이해하고 검증할 수 있음
- • 핵심 개념을 명확하게 표현함
- • 불필요한 세부사항을 제거함
- • 코드로 직접 표현 가능함
- • 새로운 요구사항에 유연하게 대응함
- • 비즈니스 규칙이 명시적으로 드러남
✗ 나쁜 모델
- • 기술적 구현 세부사항에 오염됨
- • 도메인 전문가가 이해할 수 없음
- • 너무 추상적이거나 너무 구체적임
- • 실제 코드와 동떨어져 있음
- • 변경할 때마다 전체를 수정해야 함
- • 비즈니스 로직이 여기저기 흩어져 있음
Anemic Domain Model vs Rich Domain Model
"빈약한 도메인 모델(Anemic Domain Model)은 안티패턴이다. 도메인 객체가 데이터만 가지고 행위가 없다면, 그것은 객체지향이 아니라 절차지향이다."
— Martin Fowler, Anemic Domain Model (2003)
❌ Anemic Model
// 데이터만 있는 빈약한 모델
class Order {
id: string;
items: OrderItem[];
status: string;
totalAmount: number;
}
// 로직은 서비스에 분산
class OrderService {
cancel(order: Order) {
if (order.status === 'SHIPPED') {
throw new Error('Cannot cancel');
}
order.status = 'CANCELLED';
// 환불 로직...
// 재고 복구 로직...
// 알림 발송 로직...
}
}문제: Order는 데이터 컨테이너일 뿐, 비즈니스 규칙은 Service에 흩어짐
✓ Rich Model
// 행위를 가진 풍부한 모델
class Order {
private id: OrderId;
private items: OrderItem[];
private status: OrderStatus;
cancel(): void {
// 비즈니스 규칙이 객체 안에!
if (this.status.isShipped()) {
throw new CannotCancelError();
}
this.status = OrderStatus.CANCELLED;
// 도메인 이벤트 발행
this.registerEvent(
new OrderCancelled(this.id)
);
}
}장점: Order가 자신의 규칙을 알고 있음, 응집도 높음
4. Knowledge Crunching
"Knowledge Crunching은 팀이 도메인 전문가와 함께 대량의 정보를 씹고, 가장 관련성 있는 조각들을 찾아내고, 그것들을 유용한 형태로 조직하는 탐험이다."
— Eric Evans, Domain-Driven Design, Chapter 1
Knowledge Crunching이란?
Knowledge Crunching은 도메인 전문가의 머릿속에 있는 암묵적 지식을 명시적인 모델로 변환하는 협력적 과정입니다. 이것은 일회성 활동이 아니라 프로젝트 전체에 걸쳐 지속되는 반복적 과정입니다.
도메인 전문가와 대화
비즈니스 프로세스, 규칙, 예외 상황에 대해 깊이 있는 대화를 나눔. 왜 그런 규칙이 있는지 이유를 파악.
핵심 개념 추출
대화에서 반복되는 용어, 중요한 개념, 비즈니스 규칙을 식별. 용어집(Glossary) 작성 시작.
모델 스케치
추출한 개념들을 다이어그램이나 코드로 표현해봄. 화이트보드, 포스트잇, 간단한 코드 활용.
검증과 피드백
도메인 전문가에게 모델을 보여주고 피드백을 받음. "이게 맞나요?" "이런 경우는 어떻게 되나요?"
정제와 반복
피드백을 반영하여 모델을 개선하고, 이 과정을 반복. 모델은 점점 더 깊은 통찰을 반영.
효과적인 Knowledge Crunching 기법
🎯 시나리오 워크스루
구체적인 비즈니스 시나리오를 단계별로 따라가며 모델을 검증합니다.
🔍 예외 케이스 탐색
"만약에..." 질문을 통해 엣지 케이스와 비즈니스 규칙을 발견합니다.
📝 용어 정의
도메인에서 사용하는 용어의 정확한 의미를 정의하고 문서화합니다.
🎨 Event Storming
포스트잇을 사용하여 도메인 이벤트를 중심으로 비즈니스 프로세스를 시각화합니다.
"모델은 완벽할 필요가 없다. 유용하면 된다. 그리고 모델은 시간이 지나면서 더 깊은 통찰을 반영하도록 진화해야 한다."
— Eric Evans, Domain-Driven Design, Chapter 1
Breakthrough (돌파구)
Knowledge Crunching을 계속하다 보면 갑자기 Breakthrough가 일어나는 순간이 있습니다. 기존 모델로는 설명하기 어려웠던 것들이 새로운 개념 하나로 명쾌하게 정리되는 순간입니다.
💡 Breakthrough 예시
Before: "주문 상태"를 문자열로 관리하고, 상태 변경 규칙이 여러 서비스에 흩어져 있음
After: "주문 생명주기"라는 개념을 도입하고, 상태 전이 규칙을 Order 객체 안에 캡슐화. 갑자기 모든 것이 명확해짐!
Part 2: DDD의 전체 그림
전략적 설계와 전술적 설계의 개요를 파악합니다
5. 전략적 설계 (Strategic Design) 개요
"전략적 설계는 큰 그림을 다룬다. 어떤 부분이 핵심인지, 시스템을 어떻게 분할할지, 팀 간에 어떻게 협력할지를 결정한다."
— Vaughn Vernon, Domain-Driven Design Distilled
전략적 설계는 "무엇을 만들 것인가"와 "어떻게 분할할 것인가"에 대한 높은 수준의 결정을 다룹니다. 코드를 작성하기 전에 먼저 해야 할 설계입니다.
🎯 서브도메인 (Subdomain)
전체 도메인을 논리적으로 분리한 하위 영역
핵심 경쟁력
지원 역할
범용 기능
→ 세션 3에서 자세히 다룹니다
📦 Bounded Context
특정 모델이 정의되고 적용되는 명시적 경계. 유비쿼터스 언어가 일관되게 적용되는 범위.
→ 세션 4에서 자세히 다룹니다
🗺️ Context Map
Bounded Context들 간의 관계를 시각화한 문서. 팀 간 협력 방식을 명시.
→ 세션 5에서 자세히 다룹니다
🗣️ 유비쿼터스 언어 (Ubiquitous Language)
개발자와 도메인 전문가가 공유하는 언어. 대화, 문서, 코드에서 일관되게 사용.
→ 세션 2에서 자세히 다룹니다
전략적 설계의 산출물
┌─────────────────────────────────────────────────────────────────┐ │ 이커머스 Context Map │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ Partnership ┌──────────────┐ │ │ │ 주문 │◄────────────────►│ 결제 │ │ │ │ Context │ │ Context │ │ │ │ (Core) │ │ (Core) │ │ │ └──────┬───────┘ └──────────────┘ │ │ │ │ │ │ Customer-Supplier │ │ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 배송 │ ACL │ 외부 │ │ │ │ Context │◄──────────────────│ 물류 API │ │ │ │ (Supporting) │ │ (Generic) │ │ │ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
6. 전술적 설계 (Tactical Design) 개요
"전술적 설계는 Bounded Context 내부를 어떻게 구현할지에 대한 패턴들이다. Entity, Value Object, Aggregate, Repository 등의 빌딩 블록을 사용한다."
— Vaughn Vernon, Implementing Domain-Driven Design
전술적 설계는 "어떻게 구현할 것인가"에 대한 구체적인 패턴들입니다. Bounded Context 내부에서 도메인 모델을 코드로 표현하는 방법을 제공합니다.
🔷 Entity
고유한 식별자로 구분되는 객체. 시간이 지나도 동일성 유지.
→ 세션 6에서 자세히
🔶 Value Object
속성 값으로만 정의되는 불변 객체. 식별자 없음.
→ 세션 6에서 자세히
📦 Aggregate
일관성 경계를 형성하는 객체 클러스터. Root를 통해서만 접근.
→ 세션 7에서 자세히
🗄️ Repository
Aggregate의 저장과 조회를 추상화. 컬렉션처럼 동작.
→ 세션 8에서 자세히
🏭 Factory
복잡한 객체 생성을 캡슐화. 불변식 보장.
→ 세션 8에서 자세히
⚙️ Domain Service
Entity나 VO에 속하지 않는 도메인 로직. 상태 없음.
→ 세션 9에서 자세히
⚡ Domain Event
도메인에서 발생한 중요한 사건. 과거형으로 명명.
→ 세션 10에서 자세히
📁 Module
관련된 개념들을 묶는 패키지. 높은 응집도, 낮은 결합도.
→ 세션 8에서 자세히
// Aggregate Root
class Order {
private id: OrderId; // Entity 식별자
private customerId: CustomerId; // 다른 Aggregate는 ID로 참조
private items: OrderLineItem[]; // 내부 Entity
private shippingAddress: Address; // Value Object
private status: OrderStatus; // Value Object
private events: DomainEvent[] = [];
// 비즈니스 로직이 Aggregate 안에!
addItem(product: ProductId, quantity: Quantity, price: Money): void {
if (this.status.isConfirmed()) {
throw new CannotModifyConfirmedOrder();
}
const item = new OrderLineItem(product, quantity, price);
this.items.push(item);
this.events.push(new OrderItemAdded(this.id, item));
}
confirm(): void {
if (this.items.length === 0) {
throw new CannotConfirmEmptyOrder();
}
this.status = OrderStatus.CONFIRMED;
this.events.push(new OrderConfirmed(this.id, this.totalAmount()));
}
}
// Repository Interface (도메인 계층)
interface OrderRepository {
findById(id: OrderId): Order | null;
save(order: Order): void;
nextId(): OrderId;
}전략적 설계 vs 전술적 설계
| 구분 | 전략적 설계 | 전술적 설계 |
|---|---|---|
| 관점 | 큰 그림, 시스템 전체 | 세부 구현, Context 내부 |
| 질문 | 무엇을? 어디에? 누구와? | 어떻게? |
| 산출물 | Context Map, 서브도메인 분류 | 도메인 모델 코드 |
| 참여자 | 아키텍트, 도메인 전문가, 관리자 | 개발자, 도메인 전문가 |
| 적용 시점 | 프로젝트 초기, 지속적 | 구현 단계 |
7. DDD의 핵심 원칙
1. 핵심 도메인에 집중하라
모든 것을 DDD로 할 필요는 없다. 비즈니스에 가장 중요한 핵심 도메인에 최고의 인재와 노력을 집중하라. 나머지는 단순하게 유지하거나 외부 솔루션을 활용하라.
2. 유비쿼터스 언어를 구축하라
개발자와 도메인 전문가가 같은 언어로 대화하고, 그 언어가 코드에 직접 반영되어야 한다. 언어의 변화는 모델의 변화이고, 모델의 변화는 코드의 변화다.
3. 모델과 구현을 밀접하게 연결하라
모델은 코드로 직접 표현되어야 한다. 모델과 코드가 분리되면 둘 다 가치를 잃는다. 코드가 곧 모델이고, 모델이 곧 코드여야 한다.
4. 지속적으로 모델을 정제하라
도메인에 대한 이해가 깊어지면 모델도 진화해야 한다. Refactoring Toward Deeper Insight - 더 깊은 통찰을 향한 리팩토링을 두려워하지 마라.
5. 명확한 경계를 설정하라
Bounded Context를 통해 모델의 적용 범위를 명확히 하고, 컨텍스트 간의 관계를 명시적으로 관리하라. 경계가 없으면 Big Ball of Mud가 된다.
6. 도메인 전문가와 협력하라
DDD는 혼자 할 수 없다. 도메인 전문가와의 지속적인 협력이 필수다. 그들의 지식을 코드로 옮기는 것이 우리의 일이다.
"DDD는 기술이 아니라 사고방식이다. 복잡한 도메인을 다루는 소프트웨어 프로젝트에서 성공하기 위한 원칙과 패턴의 모음이다."
— Vaughn Vernon, Implementing Domain-Driven Design
8. DDD를 적용해야 할 때와 하지 말아야 할 때
DDD가 적합한 경우
- ✓ 복잡한 비즈니스 규칙이 있는 도메인
- ✓ 도메인 전문가와 협력할 수 있는 환경
- ✓ 장기적으로 유지보수해야 하는 시스템
- ✓ 비즈니스 로직이 자주 변경되는 경우
- ✓ 팀이 DDD를 학습할 의지와 시간이 있는 경우
- ✓ 핵심 경쟁력이 되는 소프트웨어
DDD가 과도한 경우
- ✗ 단순한 CRUD 애플리케이션
- ✗ 기술적 문제가 주된 복잡성인 경우
- ✗ 도메인 전문가에 접근할 수 없는 경우
- ✗ 빠른 프로토타입이나 MVP
- ✗ 팀이 DDD 경험이 전혀 없고 일정이 촉박한 경우
- ✗ Generic Subdomain (인증, 결제 연동 등)
"DDD는 복잡성을 다루는 도구다. 복잡성이 없다면 DDD도 필요 없다. 하지만 복잡성이 있다면, DDD 없이는 그 복잡성에 압도당할 것이다."
— Eric Evans
점진적 도입 전략
1단계: 유비쿼터스 언어부터 시작
코드 변경 없이 용어집 작성, 도메인 전문가와 대화 시작
2단계: 경계 식별
Event Storming으로 Bounded Context 파악, Context Map 작성
3단계: Core Domain에 먼저 적용
가장 중요한 영역부터 전술적 패턴 적용, ACL로 레거시 격리
4단계: 점진적 확장
성공 경험을 바탕으로 다른 컨텍스트로 확장
핵심 요약
이번 세션에서 배운 것
- ✓DDD는 복잡한 도메인을 다루기 위한 설계 철학이다
- ✓소프트웨어의 본질적 복잡성은 도메인에서 온다
- ✓도메인 모델은 의사소통, 지식 저장, 구현의 기반이다
- ✓Knowledge Crunching은 지속적인 협력 과정이다
- ✓전략적 설계는 큰 그림, 전술적 설계는 구현 패턴
- ✓DDD는 복잡한 도메인에만 적용해야 한다
다음 세션 예고
다음 세션에서는 DDD의 핵심 도구인 유비쿼터스 언어(Ubiquitous Language)에 대해 깊이 있게 다룹니다.
- • 유비쿼터스 언어란 무엇인가
- • 언어를 구축하고 유지하는 방법
- • 모델과 코드에서의 언어 표현
- • 언어 불일치의 문제와 해결
기초 (1-2)
- → 1. DDD 철학과 복잡성
- 2. 유비쿼터스 언어
전략적 설계 (3-5)
- 3. 도메인과 서브도메인
- 4. Bounded Context
- 5. Context Mapping
전술적 설계 (6-9)
- 6. Entity와 Value Object
- 7. Aggregate 설계
- 8. Repository, Factory, Module
- 9. Service와 Supple Design
고급 주제 (10-12)
- 10. Domain Events
- 11. Event Sourcing과 CQRS
- 12. DDD 실천과 아키텍처
참고 자료
📚 필수 도서
- • Eric Evans, "Domain-Driven Design: Tackling Complexity in the Heart of Software" (2003) - Blue Book
- • Vaughn Vernon, "Implementing Domain-Driven Design" (2013) - Red Book
- • Vaughn Vernon, "Domain-Driven Design Distilled" (2016) - 입문서로 추천
🔗 온라인 자료
- • Martin Fowler, "Anemic Domain Model" - martinfowler.com
- • DDD Community - dddcommunity.org
- • Domain-Driven Design Reference - domainlanguage.com
- • Alberto Brandolini, "Introducing EventStorming"
🎯 실습 과제
- • 현재 프로젝트에서 "본질적 복잡성"과 "우발적 복잡성"을 구분해보세요
- • 도메인 전문가와 30분 대화하고 핵심 개념 5개를 추출해보세요
- • 현재 코드에서 Anemic Domain Model 패턴을 찾아보세요