contents
도메인 주도 설계(Domain-Driven Design, DDD) 는 소프트웨어를 특정 비즈니스 도메인에 맞게 모델링하는 데 초점을 맞추는 소프트웨어 개발 접근법입니다. 이는 기술이나 프레임워크가 아니라, 비즈니스 로직이 주요 과제인 복잡한 시스템을 구축하기 위한 철학이자 패턴들의 집합입니다. 궁극적인 목표는 비즈니스가 실제로 동작하는 방식을 그대로 반영하는 소프트웨어를 만드는 것입니다.
DDD가 해결하는 문제
많은 전통적인 소프트웨어 프로젝트에서는 데이터베이스 스키마, 프레임워크, UI와 같은 기술에 주된 초점을 맞춥니다. 비즈니스 로직은 종종 서비스 클래스, UI 이벤트 핸들러, 데이터베이스 트리거 등 코드베이스 전반에 흩어지게 됩니다. 이는 "빈약한 도메인 모델(Anemic Domain Model)"로 알려진 상태로 이어지는데, 여기서 "도메인 객체"는 단지 데이터를 담는 컨테이너(getter와 setter만 가진)에 불과하고 실제 비즈니스 규칙은 모두 다른 곳에 존재하게 됩니다.
이는 다음과 같은 여러 문제를 야기합니다.
-
코드가 비즈니스가 실제로 작동하는 방식을 반영하지 못합니다.
-
비즈니스 전문가들이 소프트웨어의 로직을 이해하거나 검증할 수 없습니다.
-
비즈니스 규칙이 변경될 때마다 코드베이스 전체를 뒤져야 하므로 유지보수가 어렵고 오류가 발생하기 쉽습니다.
DDD는 도메인과 그 로직을 프로젝트의 절대적인 중심에 둠으로써 이 문제를 해결합니다.
DDD의 두 기둥: 전략적 설계와 전술적 설계
DDD는 두 가지 주요 패턴 범주를 통해 가장 잘 이해할 수 있습니다.
-
전략적 설계 (Strategic Design - 큰 그림 🗺️): 시스템의 상위 수준 아키텍처를 정의하는 것입니다. 크고 복잡한 도메인을 더 작고 관리하기 쉬운 조각으로 나누는 데 도움을 줍니다.
-
전술적 설계 (Tactical Design - 빌딩 블록 🧱): 시스템의 잘 정의된 한 부분 내에서 객체와 그 상호작용을 설계하는 것입니다.
각각을 자세히 살펴보겠습니다.
전략적 설계: 도메인 매핑
전략적 설계는 비즈니스 도메인 자체의 모델을 만드는 것에 관한 것입니다.
1. 유비쿼터스 언어 (Ubiquitous Language)
이는 DDD에서 가장 근본적인 개념입니다. 유비쿼터스 언어는 개발자, 도메인 전문가, 그리고 다른 이해관계자들이 협력하여 개발한 공통된 언어입니다.
-
회의, 다이어그램, 문서 등 모든 형태의 의사소통에서 사용되며, 가장 중요하게는 코드 자체(클래스명, 메서드명, 변수명)에서도 사용됩니다.
-
이 언어는 엄격하고 모호하지 않아야 합니다. 비즈니스에서 어떤 것을 "Shipment(선적물)"이라고 부른다면, 코드에도
DeliveryPackage클래스가 아닌Shipment클래스가 있어야 합니다. -
이를 통해 비즈니스 요구사항과 기술적 구현 사이의 번역 오류를 제거하고, 코드에서 비즈니스 프로세스로 직접 연결되는 통로를 만듭니다.
2. 경계가 설정된 컨텍스트 (Bounded Context)
거대한 기업 전체를 위한 단일 통합 모델을 만드는 것은 비현실적이며 종종 혼란을 야기합니다. "Product(제품)"라는 단어는 다른 부서에서 다른 의미를 가질 수 있습니다.
-
영업(Sales) 부서에서 "제품"은 가격과 판매 이력을 가집니다.
-
배송(Shipping) 부서에서 "제품"은 무게, 치수, 배송 제한 사항을 가집니다.
-
지원(Support) 부서에서 "제품"은 보증 및 일반적인 문제 해결 단계를 가집니다.
경계가 설정된 컨텍스트는 특정 도메인 모델이 적용되고 유비쿼터스 언어가 명확하고 모호하지 않은 의미를 갖는 명시적인 경계입니다. 모든 부서를 위한 거대한 Product 클래스 하나를 만들려고 시도하는 대신, "영업 컨텍스트"에 맞는 Product 모델과 "배송 컨텍스트"에 맞는 다른 Product 모델을 각각 만듭니다.
3. 컨텍스트 맵 (Context Map)
컨텍스트 맵은 서로 다른 경계가 설정된 컨텍스트들 간의 관계를 보여주는 다이어그램입니다. 이는 시스템의 전체적인 아키텍처 뷰입니다. 일반적인 관계 패턴은 다음과 같습니다.
-
공유 커널 (Shared Kernel): 두 팀이 도메인 모델의 공통된 일부를 공유합니다.
-
고객-공급자 (Customer-Supplier): 한 컨텍스트(하류의 "고객")가 다른 컨텍스트(상류의 "공급자")의 서비스에 의존하고 소비합니다.
-
안티-커럽션 계층 (Anti-Corruption Layer, ACL): 다른 모델을 가진 레거시 시스템이나 외부 시스템과 통합할 때 ACL을 구축합니다. 이는 번역기 역할을 하는 코드 계층으로, 여러분의 깨끗한 도메인 모델을 다른 시스템 모델의 "오염"으로부터 보호합니다.
전술적 설계: 모델 구축
전술적 설계는 단일 경계 컨텍스트 내에서 풍부한 도메인 모델을 만들기 위한 빌딩 블록을 제공합니다.
1. 엔티티 (Entities)
엔티티는 속성이 아닌, 시간이 지나도 지속되는 고유한 식별자(identity) 로 정의되는 객체입니다. 두 엔티티는 속성이 같더라도 식별자가 다르기 때문에 다른 객체입니다.
- 예시:
Customer(고객)는 엔티티입니다. 이름이나 주소를 변경할 수 있지만, 고유한CustomerId로 식별되는 한 여전히 동일한 고객입니다.
2. 값 객체 (Value Objects)
값 객체는 식별자가 아닌 속성으로 정의되는 객체입니다. 개념적인 식별자가 없습니다. 두 값 객체는 모든 속성이 같으면 동일한 것으로 간주됩니다. 일반적으로 불변(immutable) 입니다.
- 예시:
Money(돈)는 값 객체입니다. 5달러 지폐는 다른 5달러 지폐와 서로 교환 가능합니다.Address(주소)는 또 다른 대표적인 예입니다. 주소의 "식별자"에는 관심이 없고, 오직 그 값(거리, 도시, 우편번호)에만 관심이 있습니다.
3. 애그리게잇 (Aggregates)
애그리게잇은 데이터 변경 시 단일 단위로 취급되는 연관된 엔티티와 값 객체들의 묶음입니다. 이는 일관성을 유지하기 위한 핵심 패턴입니다.
-
애그리게잇 루트 (Aggregate Root): 각 애그리게잇에는 애그리게잇 루트라는 단일 진입점인 엔티티가 있습니다. 애그리게잇 외부에서의 모든 참조는 반드시 애그리게잇 루트를 통해서만 이루어져야 합니다.
-
일관성 경계 (Consistency Boundary): 애그리게잇은 트랜잭션 경계를 정의합니다. 애그리게잇 내의 모든 객체는 함께 로드되고 저장되며, 애그리게잇 내 객체들에 걸친 모든 비즈니스 규칙(불변식)은 모든 트랜잭션에서 강제됩니다.
예시: Order(주문)는 애그리게잇 루트입니다. OrderItem(주문 항목) 엔티티 목록과 ShippingAddress(배송 주소) 값 객체를 포함할 수 있습니다. 외부에서 OrderItem을 직접 변경할 수 없으며, 반드시 Order 객체를 통해야 합니다. 예를 들어, order.changeItemQuantity(itemId, newQuantity)와 같이 호출해야 합니다. 이를 통해 Order는 "주문의 총비용은 고객의 신용 한도를 초과할 수 없다"와 같은 불변식을 강제할 수 있습니다.
4. 리포지토리 (Repositories)
리포지토리는 애그리게잇 루트에 접근하고 영속성을 관리하기 위한 컬렉션과 유사한 인터페이스를 제공하는 객체입니다. 이는 도메인 모델을 SQL 데이터베이스나 NoSQL 저장소와 같은 하위 영속성 기술로부터 분리합니다.
-
모든 객체가 메모리에 있는 것처럼 보이게 만듭니다.
-
메서드는 보통
findById(orderId)나save(order)와 같습니다. -
애그리게잇 루트 당 하나의 리포지토리가 있어야 합니다.
5. 팩토리와 서비스 (Factories and Services)
-
팩토리 (Factories): 객체나 애그리게잇의 생성이 복잡할 때, 그 로직을 팩토리에 캡슐화합니다. 이를 통해 객체가 항상 유효한 상태로 생성되도록 보장합니다.
-
도메인 서비스 (Domain Services): 때때로 비즈니스 로직이 특정 엔티티나 값 객체에 자연스럽게 속하지 않을 때가 있습니다(특히 여러 애그리게잇 간의 조정이 필요한 경우). 이 로직은 상태가 없는 도메인 서비스에 배치될 수 있습니다.
DDD의 장점과 사용 시기 ✅
장점:
-
더 나은 의사소통: 유비쿼터스 언어는 기술팀과 비즈니스팀 간의 간극을 메웁니다.
-
향상된 유지보수성: 코드 구조가 비즈니스 도메인을 직접 반영하여 이해하고 수정하기가 더 쉬워집니다.
-
증가된 유연성: 잘 분리된 도메인 모델은 변화하는 비즈니스 요구사항에 더 잘 적응할 수 있습니다.
언제 DDD를 사용해야 하는가:
-
비즈니스 복잡성이 높은 애플리케이션. 주요 과제가 기술이 아닌 도메인 규칙에 있을 때.
-
시간이 지남에 따라 진화하고 유지보수되어야 하는 장기 프로젝트.
언제 DDD를 사용하면 안 되는가:
-
비즈니스 로직이 거의 없는 간단한 CRUD(생성, 읽기, 수정, 삭제) 애플리케이션.
-
복잡성이 순전히 기술적인 문제일 때 (예: 고성능 데이터 변환). 이런 경우 DDD는 과도한 설계(오버 엔지니어링)일 수 있습니다.
references