1. DDD (Domain-Driven Design)란?
DDD는 비즈니스 도메인(문제 공간)에 대한 이해를 기반으로 소프트웨어를 설계하는 방법론입니다. 이 방법론의 목적은 복잡한 도메인을 해결하기 위한 설계를 통해 소프트웨어 시스템의 가치를 극대화하는 것입니다.
도메인은 개발해야 할 문제 공간을 의미합니다. DDD의 핵심 목표는 소프트웨어 설계를 현실 세계의 도메인과 최대한 일치시켜, 도메인의 복잡한 규칙과 로직을 쉽게 관리하고 이해할 수 있도록 돕는 것입니다.
DDD의 주요 목표는 도메인 지식을 깊이 있게 반영하여 비즈니스 요구 사항을 충족하는 시스템을 구축하는 것입니다. 이를 위해서 개발자와 도메인 전문가가 같은 언어로 소통하는 것이 중요합니다. 이를 '유비쿼터스 언어(Ubiquitous Language)'라고 합니다.
1.1 DDD의 기본 원칙
- 도메인 모델: 비즈니스 로직을 표현하는 모델을 설계하고 이를 기반으로 시스템을 구축합니다.
- 유비쿼터스 언어(Ubiquitous Language): 도메인 전문가와 개발자 간의 원활한 의사소통을 위해 동일한 용어를 사용합니다.
- Bounded Context: 도메인 모델을 여러 개의 컨텍스트로 나누어 복잡도를 관리합니다.
- 전략적 설계와 전술적 설계로 나뉩니다.
1.2 주요 용어 설명
- 도메인(Domain): 해결해야 할 문제의 주제 영역입니다. 예를 들어, 은행 시스템에서는 "계좌", "대출", "송금" 등이 도메인이 될 수 있습니다.
- 도메인 모델(Domain Model): 도메인의 문제를 해결하기 위한 논리적 구조입니다. 도메인 모델은 비즈니스 규칙과 로직을 정의하며, 소프트웨어에서 이를 구현하는 방식입니다.
- 유비쿼터스 언어(Ubiquitous Language): 개발자와 비즈니스 관계자가 동일한 언어로 소통할 수 있게 만든 공통된 용어 체계입니다. 이 언어를 통해 도메인 전문가와 개발자 간의 의사소통을 원활하게 합니다.
- Bounded Context(경계된 문맥): 도메인 모델이 하나의 시스템 전체에 적용되는 것이 아니라, 각 문맥(Bounded Context)에 따라 모델이 달라질 수 있음을 정의합니다. 즉, 특정 영역에서 사용되는 도메인 모델과 비즈니스 로직이 다른 영역과 분리되어 독립적으로 동작할 수 있습니다. 예를 들어, '결제 시스템'과 '상품 관리 시스템'은 각각 별개의 문맥을 가질 수 있습니다.
- Context Map(문맥 지도): 여러 Bounded Context들 간의 관계를 표현하는 시각적 지도입니다. 이를 통해 시스템 내에서 문맥 간의 경계와 상호작용을 명확히 파악할 수 있습니다.
2. DDD에 대한 오해
2.1 DDD는 전술적 패턴만 포함한다?
전술적 설계(예: Aggregates, Entities, Repositories, Domain Events)는 DDD의 일부일 뿐, 전체는 아닙니다. 전략적 설계(Bounded Context, Context Mapping)가 더 중요합니다.
2.2 DDD는 모든 문제를 해결할 수 있는 은탄환이다?
DDD는 복잡한 도메인을 관리하는데 도움이 되지만, 모든 프로젝트에 무조건 적용되는 만능 해결책은 아닙니다.
2.3 DDD는 MSA (Microservices Architecture)로 귀결된다?
DDD는 필연적으로 MSA로 이어지지 않으며, 특정 프로젝트에서 MSA가 적합하지 않다면 다른 아키텍처 스타일과 함께 사용할 수 있습니다.
2.4 DDD는 기술적인 방법론이다?
DDD는 기술보다는 도메인 지식과 설계 철학에 가까운 접근 방식이다.
3. 전술적 설계와 전략적 설계
3.1 전술적 설계
전술적 설계는 구체적인 소프트웨어 구현 방법에 집중하는 부분입니다. 여기에는 도메인을 모델링하고 구현하는 구체적인 패턴들이 포함됩니다. 즉, 실제로 시스템을 구현할 때, 각 도메인 모델을 어떻게 구성할지를 정의합니다.
1) 주요 용어 정리
- 엔티티(Entity): 고유한 식별자를 가지는 도메인 객체입니다. 엔티티는 상태(속성)와 행동(메서드)을 포함합니다. 예를 들어, '사용자'는 고유한 사용자 ID를 가진 엔티티입니다. 사용자 엔티티에는 이름, 이메일 등의 속성이 있고, 이메일 변경과 같은 행동을 수행할 수 있습니다.
- 값 객체(Value Object): 값 객체는 고유 식별자 없이 값 자체가 의미를 가지는 객체입니다. 값 객체는 불변(immutable)하며, 예를 들어 '주소'는 값 객체로 다루어질 수 있습니다. 주소는 '서울시 강남구'처럼 값 자체로만 비교되며, 고유한 식별자가 필요 없습니다.
- 애그리게이트(Aggregate): 여러 엔티티와 값 객체를 묶어 하나의 단위로 관리하는 도메인 모델의 클러스터입니다. 애그리게이트는 도메인 로직을 일관되게 관리하고, 변경 시 다른 엔티티에 영향을 주지 않도록 도메인 간의 경계(Boundary)를 설정합니다. 애그리게이트에는 항상 루트 엔티티(Aggregate Root)가 존재하며, 이 루트 엔티티만이 외부에서 접근 가능합니다.
- 예를 들어, '주문' 애그리게이트는 '주문 엔티티', '주문 항목 엔티티' 등을 포함하고, '주문' 엔티티가 루트로 동작합니다. 이 루트 엔티티만 외부에서 접근할 수 있고, 내부의 다른 엔티티는 직접 접근할 수 없습니다.
- 리포지토리(Repository): 애그리게이트를 영속성(Persistence) 계층에 저장하고 불러오는 역할을 합니다. 리포지토리는 데이터베이스 액세스를 캡슐화하여, 도메인 모델에서 직접 데이터베이스와 상호작용하지 않도록 합니다.
- 도메인 이벤트(Domain Event): 도메인 모델에서 발생한 중요한 사건이나 변화를 나타냅니다. 예를 들어, '주문이 생성됨' 또는 '결제가 완료됨'과 같은 이벤트가 발생할 수 있습니다. 도메인 이벤트는 비즈니스 규칙이 동작하는 순간을 나타내며, 이를 통해 다른 시스템이나 서비스가 트리거될 수 있습니다.
- 도메인 서비스(Domain Service): 엔티티나 값 객체가 담당하기 어려운 비즈니스 로직을 처리하는 역할을 합니다. 도메인 서비스는 주로 도메인 규칙을 표현하며, 예를 들어 '배송비 계산'과 같은 로직이 도메인 서비스에 포함될 수 있습니다.
- 팩토리(Factory): 애그리게이트나 엔티티의 복잡한 생성 로직을 캡슐화하는 객체입니다. 복잡한 객체 생성 과정을 외부에서 쉽게 호출할 수 있도록 도와줍니다.
3.2 전략적 설계
도메인의 경계를 정의하고, 각 경계(컨텍스트)를 효과적으로 구분하고 관리하는 것이 중요합니다. 이 접근법을 통해 복잡한 시스템을 분리하고, 각 부분의 책임을 명확히 하여 유연성과 확장성을 유지합니다.
1) 주요 용어 정리
- Bounded Context: 위에서 설명한 대로, 도메인 모델이 여러 Bounded Context로 나누어질 수 있습니다. 각 Bounded Context는 독립적으로 도메인 모델을 가지며, 각 문맥에서만 사용되는 특정한 규칙을 적용합니다.
- 컨텍스트 맵(Context Map): 여러 Bounded Context 간의 관계와 상호작용을 명확히 보여주는 지도입니다. 이를 통해 Bounded Context 간의 의존성, 협력 방식 등을 파악할 수 있습니다.
4. Bounded-Context와 Context Map
Bounded Context는 DDD에서 가장 중요한 개념 중 하나로, 하나의 도메인을 여러 개의 하위 도메인으로 분리하여 모델의 경계를 정의하는 것입니다.
- 예를 들어, 티켓 예매 시스템에서 "회원 관리", "상품 정보", "결제" 등의 도메인을 별도의 Bounded Context로 나눌 수 있습니다.
- Context Map은 여러 Bounded Context 간의 관계를 시각화하여 설계의 방향을 명확히 합니다.
5. DDD 적용시 고려사항
5.1 지식 탐구 = 지식 뽀개기 (Knowledge Crunching)
도메인 전문가와의 소통을 통해 문제 영역에 대한 깊은 지식을 확보하는 과정입니다. 이를 통해 정확한 도메인 모델을 설계할 수 있습니다.
5.2 Big Ball of Mud
시스템에 구조적 설계가 없이 복잡한 관계로 얽혀 있는 상태를 가리킵니다. DDD는 이런 상황을 해결하는 데 도움을 줄 수 있습니다.
5.3 콘웨이의 법칙 (Conway’s Law)
소프트웨어 시스템의 구조는 조직의 커뮤니케이션 구조를 반영한다는 법칙입니다. 즉, 팀 간의 협력 구조가 소프트웨어 아키텍처에 큰 영향을 미칩니다.
6. 진화하는 설계
DDD는 설계를 한 번에 완벽하게 하는 것이 아니라, 도메인 지식의 변화에 따라 설계를 지속적으로 개선하는 방식을 권장합니다. 비즈니스 환경이 변화함에 따라 도메인 모델을 업데이트하고, 설계를 점진적으로 발전시켜 나가는 것이 중요합니다.
6.1 DDD가 해결하는 문제
DDD는 복잡한 도메인을 다룰 때 발생하는 문제를 해결하는 데 특히 효과적입니다.
- 복잡한 비즈니스 로직을 명확하게 모델링하고, 이를 통해 시스템이 비즈니스 목표를 정확하게 달성할 수 있게 합니다.
- 유비쿼터스 언어를 통해 개발자와 비즈니스 전문가 간의 소통을 개선하여, 도메인에 대한 이해를 일관되게 유지합니다.
- Bounded Context를 활용해 시스템의 복잡도를 관리하고, 각 도메인이 독립적으로 확장될 수 있는 유연한 아키텍처를 제공합니다.
6.2 DDD 적용의 예
만약 '온라인 예매 시스템'을 개발한다고 가정해 봅시다.
- 도메인은 '예매', '회원 관리', '결제'와 같은 하위 도메인으로 나뉠 수 있습니다.
- '예매 도메인'은 '예매 엔티티', '티켓 엔티티'를 포함한 애그리게이트로 구성될 수 있으며, 이를 관리하는 '예매 리포지토리'를 통해 데이터베이스에 저장됩니다.
- '예매 완료'와 같은 도메인 이벤트는 결제 시스템과의 통합을 트리거할 수 있습니다.
이처럼 DDD를 통해 비즈니스 문제를 반영한 소프트웨어 아키텍처를 설계할 수 있습니다.