안녕하세요, Devloo입니다. 여러분은 혹시 “DDD”라는 말을 들어보신 적 있으신가요? DDD는 도메인 주도 설계(Domain Driven Design)의 약자입니다. 소프트웨어 개발에서 DDD는 복잡한 비즈니스 로직을 효과적으로 관리하고 구현하는 방법론으로, 많은 개발자들에게 주목받고 있습니다.
이 글에서는 DDD의 기본 개념을 쉽고 명확하게 설명하려고 합니다. 에릭 에반스의 DDD 서적에서 다루는 모든 내용을 깊이 있게 다루지는 않지만, 핵심 개념을 이해하고 시작하는 데 큰 도움이 될 것입니다. 이 글을 통해 DDD에 대한 이해를 높이고, 더 깊이 있는 학습을 원하신다면 관련 서적을 참고해 보시기 바랍니다. 자 그럼 시작해볼까요 🙂 ?
주제
Chapter 1
— 도메인 주도 설계란 무엇인가?
— 도메인과 서브도메인
— 유비쿼터스 언어란 무엇인가?
— 코어 도메인, 지원 도메인, 일반 도메인의 유형
— 바운디드 컨텍스트란 무엇인가?
— 컨텍스트와 서브 컨텍스트
— 컨텍스트 맵
Chapter 2
— 엔티티와 정체성 (Entity and Identity)
— 값 객체 (Value Objects)
— 서비스 (Service)
— 모듈 (Module)
— 애그리거트 (Aggregate)
— 리포지터리 (Repository)
Chapter 3
— 핵심 분리 (Core Segregation)
— 부패 방지 계층 (Anti-corruption Layer)
— 트랜잭션 레이어 (Transaction Layer)
— 바운디드 컨텍스트 통합
도메인 주도 설계(DDD)란 무엇인가?
먼저 도메인이란 무엇일까요?
도메인이란 소프트웨어 시스템이 해결하고자 하는 특정 비즈니스 문제나 주제를 의미합니다. 쉽게 말해, 도메인은 소프트웨어가 다루는 비즈니스 영역 또는 분야를 가리킵니다. 예를 들어, 온라인 쇼핑몰에서는 제품 관리, 주문 처리, 결제 시스템 등이 모두 도메인이 될 수 있습니다.
도메인은 비즈니스에 필수적인 개념, 규칙, 프로세스의 집합을 포함하며, 이를 정확히 이해하고 소프트웨어 설계에 반영하는 것이 중요합니다.
이 정의를 이해한 후, 이제 DDD가 무엇인지 이야기해 봅시다.
DDD는 도메인을 중심으로 소프트웨어를 설계하여 비즈니스 요구사항을 효과적으로 반영하고 문제를 해결할 수 있게 합니다. 이를 위해 특정 코드 구조를 강제하지 않으면서도 다양한 패턴과 제안을 통해 도메인 모델의 핵심 포인트, 비즈니스 규칙, 도메인 논리 내의 모든 관계를 파악하는 데 중점을 둔 아키텍처 스타일입니다.
도메인과 서브도메인 (Domains and Subdomain)
도메인이란 무엇인가?
간단히 말해, 도메인은 소프트웨어가 속하는 특정 지식이나 비즈니스 영역을 의미합니다. 이는 DDD의 중심 요소입니다.
도메인 전문가들은 개발자와 협력하여 비즈니스 규칙, 범위, 프로세스 등 핵심 비즈니스 개념을 식별하고 이해합니다.
도메인의 예시:
- 병원 관리 소프트웨어
- 온라인 스토어
- 학교 관리 소프트웨어
서브도메인이란 무엇인가?
큰 도메인을 다룰 때는 비즈니스 규칙과 기능을 고려하여 범위를 더 작고 관련된 범위로 나누는 것이 좋습니다.
각 서브도메인은 고유의 기능과 범위를 관리하며, 다른 서브도메인과 연관되어 부모 도메인의 일부를 형성합니다.
서브도메인의 예시는 다음과 같습니다:
- 도메인: 이커머스
- 서브도메인: 제품 관리
- 서브도메인: 주문 관리
이렇게 도메인과 서브도메인으로 나누면 복잡한 시스템을 더 체계적으로 관리할 수 있습니다.
유비쿼터스 언어 (Ubiquitous language)
유비쿼터스 언어란 개발자와 도메인 전문가가 아이디어를 공유할 때 사용하는 공통 언어를 의미합니다. 이는 프로그래밍 언어와는 무관하며, 도메인의 모든 구성 요소에 대한 용어 정의를 말합니다. 또한 유비쿼터스 언어는 회의, 기획, 디자인, 개발 등 프로젝트 내부의 언제 어디서나 사용되기 위해 정의된 언어라고 볼 수 있습니다.
주요 포인트는 다음과 같습니다:
- 기술적이지 않은 용어 사용
- 용어집 정의
- 도메인 전문가와 개발자 간의 지속적인 협력
- 언어를 최신 상태로 유지
유비쿼터스 언어는 모든 영역에서 동일한 언어를 사용함으로써 오해를 최소화하는 데 매우 유용합니다.
예시:
이커머스 도메인에 “제품 관리”와 “주문 관리”라는 두 개의 서브도메인이 있다고 가정해봅시다. 이제 각 서브도메인에 대한 유비쿼터스 언어를 정의해보겠습니다.
도메인: 이커머스
서브도메인: 제품 관리
“제품 관리” 서브도메인의 유비쿼터스 언어:
- 제품: 이커머스에서 판매 가능한 상품
- 카탈로그: 사용 가능한 제품의 모음
- 재고: 재고에 있는 제품의 수량
- 카테고리: 제품의 유형이나 공통 특성에 따른 분류
- SKU (Stock Keeping Unit): 재고 내 제품의 고유 식별자
- 가격: 제품에 할당된 금전적 가치
- 설명: 제품을 설명하는 상세 정보
서브도메인: 주문 관리
“주문 관리” 서브도메인의 유비쿼터스 언어:
- 주문: 고객이 한 개 이상의 제품을 구매 요청하는 것
- 쇼핑 카트: 고객이 주문을 완료하기 전에 선택한 제품의 임시 목록
- 주문 확인: 고객의 주문을 검증하고 등록하는 작업
- 주문 상태: 주문의 현재 상태, 대기 중, 처리 중, 배송 중 또는 배달 완료
- 결제: 주문에 대한 결제 처리
- 배송: 주문한 제품을 준비하고 배송하는 과정
- 배송지: 제품이 배송될 위치
- 고객: 이커머스에서 거래를 통해 주문을 하는 사람 또는 단체
- 송장: 구매한 제품의 가격과 세부 정보를 표시하는 문서로, 고객이 청구 및 세금 목적으로 사용
이와 같이 유비쿼터스 언어를 정의하면 개발자와 도메인 전문가 간의 의사소통이 원활해지고, 오해가 줄어들어 효율적인 협업이 가능합니다.
도메인/서브도메인의 유형 — 핵심, 지원, 일반 (Core, Support, and Generic)
도메인 및 서브도메인을 정의할 때 주요 비즈니스의 중요성과 영향에 따라 기능을 그룹화하는 데 도움이 되는 세 가지 범주가 있습니다: 핵심, 지원, 일반. 각 범주에 대해 설명하고 실제 예시를 들어보겠습니다.
핵심(Core) 도메인/서브도메인
핵심 도메인은 시스템의 본질로, 설계 및 개발 과정에서 특별한 주의를 기울이는 부분입니다. 이는 비즈니스의 핵심으로, 시스템에 차별화된 가치를 제공하는 주요 기능과 프로세스를 포함합니다. 이 도메인은 비즈니스의 핵심적이고 중요한 측면을 다루며, 복잡하고 전문적인 비즈니스 규칙에 집중합니다.
지원(Support) 도메인/서브도메인
지원 도메인은 시스템의 일반적인 운영에 필요한 부차적인 기능을 포함하지만, 비즈니스의 중심적이거나 차별화된 부분은 아닙니다. 이 기능들은 주로 인프라, 사용자 관리, 보안, 알림 등을 포함합니다. 비즈니스의 핵심은 아니지만, 시스템 전체의 원활한 작동과 지원을 위해 중요합니다.
일반(Generic) 도메인/서브도메인
일반 도메인은 시스템 내 여러 도메인에 적용될 수 있는 일반적이고 재사용 가능한 구성 요소 및 기능을 의미합니다. 이러한 기능들은 공통적이고 일반적인 솔루션으로, 비즈니스에 직접적인 가치를 제공하지 않습니다. 일반 도메인의 예로는 유틸리티 라이브러리, 인프라 구성 요소, 인증 서비스, 결제 제공업체와의 연동 등이 있습니다.
예시: 이커머스 시스템
- 핵심 도메인: 제품 관리, 주문 처리 등 비즈니스의 본질적인 부분
- 지원 도메인: 사용자 관리, 보안, 알림 등 시스템 운영을 지원하는 부분
- 일반 도메인: 인증 서비스, 결제 통합 등 여러 도메인에서 재사용 가능한 부분
이처럼 도메인과 서브도메인을 유형별로 나누면 시스템을 더 체계적으로 관리할 수 있습니다.
바운디드 컨텍스트 (Bounded Context)
바운디드 컨텍스트는 DDD의 핵심 개념 중 하나로, 도메인 모델의 특성과 기능을 한정합니다. 각 바운디드 컨텍스트는 고유한 유비쿼터스 언어를 정의합니다.
복잡한 소프트웨어 시스템에서는 사용되는 컨텍스트에 따라 다양한 개념, 규칙, 용어가 서로 다른 의미를 가질 수 있습니다. 이 때문에 각 바운디드 컨텍스트는 엔티티, 값 객체, 애그리거트, 서비스 등 해당 컨텍스트에 관련 있고 의미 있는 항목들로 구성된 자체 도메인 모델을 갖게 됩니다. 바운디드 컨텍스트 내의 도메인 모델은 특정 비즈니스 요구와 규칙을 고려하여 설계되고 최적화됩니다.
바운디드 컨텍스트는 명확하게 정의된 인터페이스를 통해 서로 통신하며, 상호 작용의 경계를 명확히 설정합니다. 이를 통해 각기 다른 컨텍스트들이 독립적으로 개발, 발전, 유지될 수 있으며, 다른 컨텍스트에 영향을 주지 않습니다. 다음 몇 섹션에서 이 부분에 대해 좀 더 깊이 다루겠습니다.
컨텍스트와 서브컨텍스트 (Context and Subcontext)
컨텍스트
컨텍스트는 시스템의 다양한 영역 간 명확한 경계와 분리를 정의하여 복잡성을 이해하고 관리하기 쉽게 만듭니다. 이는 특정 영역을 나타내는 도메인 모델의 기능을 한정합니다. 각 컨텍스트는 해당 컨텍스트에 관련된 고유의 엔티티, 애그리거트, 서비스, 값 객체 등으로 구성된 도메인 모델을 갖습니다.
서브컨텍스트
서브컨텍스트는 더 큰 컨텍스트를 작은 단위로 세분화한 것으로, 기능의 유사성과 논리적 관계에 따라 그룹화하고 조직하는 데 도움을 줍니다. 서브컨텍스트를 사용하면 큰 컨텍스트를 더 작고 관리하기 쉬운 부분으로 나눌 수 있어 시스템을 설계하고 유지보수하는 데 유리합니다.
일반적으로 컨텍스트와 서브컨텍스트의 정의 방식은 시스템과 비즈니스에 따라 다를 수 있습니다. 컨텍스트와 서브컨텍스트의 구분은 비즈니스 도메인과 다양한 개념 및 기능 간의 상호작용에 대한 깊은 이해를 바탕으로 해야 합니다.
컨텍스트 맵 (Context Maps)
개별 바운디드 컨텍스트에 집중하다 보면 전체를 놓칠 때가 있습니다. 이를 방지하기 위해 비즈니스를 전체적으로 조망할 수 있는 지도가 필요한데, 이것이 바로 컨텍스트 맵입니다.
컨텍스트 맵은 바운디드 컨텍스트 간의 관계를 시각적으로 보여줍니다.
- OHS: 오픈 호스트 서비스 (Upstream)
- ACL: 부패 방지 계층 (Downstream)
컨텍스트 맵은 시스템의 전체 구조를 나타냅니다. 컨텍스트 맵을 그리는 데는 특별한 규칙이 없으며, 간단한 도형과 선을 사용하여 각 컨텍스트의 관계를 이해할 수 있는 수준에서 그리면 됩니다. 컨텍스트 맵은 단순하기 때문에 화이트보드나 파워포인트와 같은 도구를 이용해 쉽게 작성할 수 있습니다.
컨텍스트 맵은 전체 시스템에 대한 이해를 높여줍니다. 즉, 시스템을 더 잘 이해하거나 시간이 지나면서 컨텍스트 간 관계가 변경되면 컨텍스트 맵도 함께 업데이트해야 합니다.
엔티티와 정체성 (Entity and Identity)
엔티티는 DDD의 핵심 개념 중 하나로, 도메인 모델링에서 중요한 역할을 합니다. 이를 통해 주요 비즈니스 개념을 표현하고 다룰 수 있습니다.
에릭 에반스는 “엔티티는 시간에 따라 고유하고 지속적인 정체성을 가진 객체이다. 엔티티는 독립적인 존재를 가지며, 그 식별자를 통해 다른 엔티티와 구별될 수 있다”고 말합니다.
좀 더 설명하자면, 엔티티는 비즈니스 도메인의 개념을 나타내는 객체로 고유한 정체성을 가집니다. 이 정체성 덕분에 유사한 속성과 값을 가진 다른 엔티티와 구별할 수 있습니다. 엔티티는 시간이 지나면서 상태가 변할 수 있지만, 정체성은 변하지 않습니다.
엔티티는 비즈니스 도메인 내 특정 개념과 관련된 데이터와 행동을 모두 캡슐화합니다. 예를 들어, 온라인 판매 시스템에서 “제품” 객체는 제품 번호, 가격, 브랜드, 설명 등의 속성을 가질 수 있습니다. “제품” 엔티티는 구매, 재고 확인 등의 작업을 수행하는 메서드를 가질 수 있습니다.
정체성 (Identity)
정체성은 엔티티를 고유하고 다른 엔티티와 구별할 수 있게 하는 속성 또는 속성들의 집합을 의미합니다. 이는 시스템 내에서 엔티티를 고유하게 식별할 수 있는 특성입니다. 예를 들어, “온라인 스토어” 도메인에서 “주문” 엔티티는 각 주문에 할당된 고유한 주문 번호로 정체성을 가질 수 있습니다.
예시:
호텔 시스템을 상상해 봅시다. 여기에는 호텔 예약(핵심 도메인)과 청구(지원 도메인)라는 두 가지 컨텍스트가 있습니다.
### 핵심 도메인: 호텔 예약
엔티티:
- 예약
- ID
- 체크인 날짜
- 체크아웃 날짜
- 고객
- 객실
- 가격
- 호텔
- ID
- 이름
- 주소
- 고객
- ID
- 이름
- 이메일
- 전화번호
### 지원 도메인: 청구
엔티티:
- 청구서
- ID
- 고객
- 날짜
- 청구 내역
- 고객 (주 컨텍스트에서 재사용)
각 엔티티는 비즈니스 모델의 표현으로, 이 경우 예약, 호텔, 고객, 청구서입니다. 각 엔티티는 ID 속성을 가지며, 이 속성은 엔티티와 관련된 데이터를 고유하게 식별하는 정체성입니다.
값 객체 (Value Objects)
값 객체(Value Objects)는 DDD의 중요한 구성 요소로, 고유한 정체성이 없는 비즈니스 도메인의 구성 요소를 모델링하는 데 사용됩니다. 엔티티와 달리 값 객체는 그 속성과 특성으로 정의됩니다.
어떤 개념이 값 객체인지 결정하려면 다음 특성을 대부분 가지고 있는지 확인해야 합니다:
- 도메인에서 어떤 것을 측정, 수량화, 또는 설명합니다.
- 불변으로 유지될 수 있습니다.
- 관련 속성을 통합하여 개념적 전체를 모델링합니다.
- 측정이나 설명이 변경될 때 완전히 교체될 수 있습니다.
- 값 동등성(Value equality)을 사용하여 다른 값 객체와 비교할 수 있습니다.
- 부작용 없는 행동(Side-Effect-Free Behavior)을 제공합니다.
일반적인 값 객체의 예로는 다음과 같은 것들이 있습니다:
- 주소: 거리, 도시, 주, 우편번호와 같은 속성으로 구성됨
- 날짜 범위: 시작일과 종료일로 정의됨
- 통화: 통화 코드와 금액과 같은 속성을 가짐
예시:
앞서 정의한 예시를 기반으로 필요한 값 객체를 추가해 보겠습니다.
#### 핵심 도메인: 호텔 예약
엔티티:
- 예약
- ID
- 체크인 날짜
- 체크아웃 날짜
- 고객
- 객실
- 가격
- 호텔
- ID
- 이름
- 주소
- 고객
- ID
- 이름
- 이메일
- 전화번호
값 객체:
- 체크인 날짜
- 날짜
- 체크아웃 날짜
- 날짜
- 객실
- 번호
- 유형
- 가격
- 금액
- 통화
#### 지원 도메인: 청구
엔티티:
- 청구서
- ID
- 고객
- 날짜
- 청구 내역
- 고객 (주 컨텍스트에서 재사용)
값 객체:
- 청구 내역
- 설명
- 금액
- 총액
- 금액
- 통화
보시다시피, 정의된 값 객체는 고유한 정체성을 가지지 않습니다.
서비스 (Service)
도메인에서 서비스는 특정 작업을 수행하는 무상태(stateless) 작업입니다.
서비스는 특정 객체나 엔티티에 자연스럽게 맞지 않는 복잡한 비즈니스 로직과 작업을 캡슐화하는 데 사용되는 핵심 요소입니다. 이는 컨텍스트 내의 다양한 객체와 엔티티를 조정하고 관리하는 데 도움을 줍니다.
엔티티와 값 객체가 정체성과 속성으로 도메인 개념을 표현하는 것과 달리, 서비스는 도메인에서 수행되는 작업, 운영 및 행동에 중점을 둡니다. 이러한 작업은 특정 엔티티와 직접적으로 관련이 있을 수도 있고, 그렇지 않을 수도 있습니다.
예시:
호텔 시스템을 예로 들어보겠습니다. 엔티티와 값 객체의 목록은 생략하고 서비스에만 집중하겠습니다.
애플리케이션: 호텔 예약 플랫폼
핵심 도메인: 호텔 예약
엔티티: 예약, 호텔, 고객
값 객체: 체크인 날짜, 체크아웃 날짜, 객실, 가격
서비스: 예약 서비스
-- 서비스 기능:
-- MakeReservation()
-- SendReservationEmail()
-- CancelReservation()
-- SendCancelationEmail()
-- ConsultAvailability() ##상담 가능 여부
지원 도메인: 청구
엔티티: 청구서, 고객
값 객체: 청구 항목, 총액
서비스: 청구 서비스
-- 서비스 기능:
-- GenerateBilling()
보시다시피, 서비스는 엔티티의 작업을 정의하는 함수들을 선언하며, 이러한 작업들은 비즈니스 로직과 관련이 있고 도메인에 대해 정의된 유비쿼터스 언어로 표현됩니다.
모듈 (Module)
Java나 C#을 사용해본 적이 있다면 모듈에 익숙할 것입니다. 다만 다른 이름으로 알고 있을 뿐입니다. Java와 Go에서는 이를 패키지라 부르고, C#에서는 네임스페이스라 부릅니다.
DDD 컨텍스트에서 모듈은 높은 응집력을 가지는 도메인 객체 클래스를 위한 명명된 컨테이너 역할을 합니다.
목표는 서로 다른 모듈에 있는 클래스들 간의 낮은 결합도를 유지하는 것입니다. DDD에서 사용하는 모듈은 단순한 저장 공간이 아니기 때문에, 모듈의 이름을 적절하게 짓는 것도 중요합니다. 모듈의 이름은 유비쿼터스 언어의 중요한 부분이 됩니다.
에릭 에반스는 다음과 같이 말합니다: “시스템의 이야기를 전하고 응집력 있는 개념 세트를 포함하는 모듈을 선택하십시오. 이는 종종 모듈 간의 낮은 결합도를 가져오지만, 그렇지 않다면 모델을 변경하여 개념을 분리하는 방법을 찾아보십시오. . . . 모듈에 유비쿼터스 언어의 일부가 되는 이름을 부여하십시오. 모듈과 그 이름은 도메인에 대한 통찰력을 반영해야 합니다.”
간단히 말해, 모듈은 동일한 컨텍스트의 일부를 구성하는 기능을 그룹화하여 엔티티, 서비스, 값 객체 등을 그들의 컨텍스트 정의에 따라 조직하는 데 사용됩니다.
예시:
호텔 시스템을 예로 들어 다음과 같은 모듈을 정의할 수 있습니다:
호텔 시스템
-- 모듈: 호텔 예약
-- 엔티티 (entity)
-- 값 객체 (value object)
-- 서비스 (service)
-- 저장소 (repository)
-- 애그리거트 (aggregate)
-- 모듈: 청구
-- 엔티티 (entity)
-- 값 객체 (value object)
-- 서비스 (service)
-- 저장소 (repository)
-- 애그리거트 (aggregate)
애그리거트 (Aggregate)
이 섹션을 다음 문장으로 시작해 보겠습니다. “엔티티와 값 객체를 애그리거트로 묶어 일관성 경계를 신중하게 설정하는 것은 처음에는 간단해 보일 수 있습니다. 그러나 DDD(도메인 주도 설계) 전술 지침 중에서도 이 패턴은 가장 이해하기 어려운 것 중 하나입니다.”
이제 이 개념이 무엇인지와 그 용도에 대해 간단히 설명드리겠습니다. 하지만 서두에서 언급했듯이, 이는 DDD에서 가장 복잡한 부분 중 하나입니다. 제대로 이해하고 사용하려면 이 중요한 부분을 깊이 살펴보시길 권장합니다.
애그리거트는 엔티티와 값 객체를 논리적으로 관련된 객체들로 그룹화하여 도메인 모델에서 일관된 단위로 취급할 수 있게 해주는 “디자인 패턴”입니다. 이는 도메인의 특정 맥락 내에서 일관성과 무결성을 보장하기 위해 사용됩니다.
애그리거트는 생애 주기의 모든 단계에서 불변성을 유지해야 하는 범위를 정의합니다. 이후의 패턴인 공장(Factories)과 저장소(Repositories)는 애그리거트를 기반으로 작동하며, 특정 생애 주기 전환의 복잡성을 감추어 줍니다.
애그리거트를 설계할 때 다음과 같은 중요한 요소들을 고려해야 합니다:
- 대규모 애그리거트
- 애그리거트에 많은 수의 엔티티나 값 객체가 포함되고 복잡성이 높은 경우, 이를 관리하고 유지하기가 어렵습니다. 일관성, 확장성, 동시성 등을 개선하기 위해 애그리거트를 더 작은 단위로 분할하는 것이 좋습니다.
- 다중 애그리거트
- 도메인 모델에는 여러 애그리거트가 존재할 수 있으며, 각 애그리거트는 관련 객체들을 응집된 집합으로 캡슐화하고 도메인의 특정 부분에 집중합니다. 다중 애그리거트를 사용하면 시스템의 다양한 부분 간의 응집성과 격리(결합)를 유지할 수 있습니다.
- 규칙: 일관성 경계 내에서 진정한 불변성을 모델링하라
- 이 규칙은 도메인 모델의 진정한 불변성을 애그리거트의 일관성 경계 내에서 모델링해야 한다고 제안합니다. 애그리거트는 비즈니스 규칙과 제약 조건이 경계 내에서 준수되도록 책임을 집니다. 이를 통해 시스템 무결성을 유지하고 데이터 불일치를 방지할 수 있습니다.
- 규칙: 작은 애그리거트 설계
- 작고 단일 책임에 집중된 애그리거트를 설계하는 것이 좋습니다. 작은 애그리거트는 이해하기 쉽고 유지 보수와 확장이 용이합니다. 또한, 서로를 차단하지 않고 동시에 조작할 수 있어 더 큰 동시성을 허용합니다.
- 규칙: ID로 다른 애그리거트 참조
- 애그리거트는 다른 애그리거트의 내부 객체를 직접 참조하는 대신, 그 정체성(ID)으로 참조해야 합니다. 이를 통해 관련 객체에 접근할 때 애그리거트의 전체 구조를 로드하고 관리할 필요가 없어지며, 애그리거트 간의 복잡성과 결합도를 줄일 수 있습니다.
- 규칙: 경계 외부에서 최종 일관성(Eventual Consistency) 사용
- 애그리거트 경계 외부에서는 즉각적인 일관성 대신 최종 일관성을 사용할 수 있습니다. 이는 애그리거트 간 업데이트가 지연될 수 있고 즉시 일관되지 않을 수 있음을 의미합니다. 이를 통해 분산 시스템에서 확장성과 성능을 높일 수 있습니다.
에릭 에반스는 그의 책에서 다음과 같이 언급합니다. “엔티티와 값 객체를 애그리거트로 클러스터링하고 각 애그리거트 주위에 경계를 정의하십시오. 각 애그리거트의 루트가 될 하나의 엔티티를 선택하고, 경계 내 객체에 대한 모든 접근을 루트를 통해 제어하십시오. 외부 객체는 루트에 대한 참조만 가지도록 허용하십시오. 일시적인 참조는 단일 작업 내에서만 사용될 수 있도록 전달될 수 있습니다. 루트가 접근을 제어하므로 내부 변경에 의한 영향을 받지 않습니다. 이러한 배열은 애그리거트 및 애그리거트 전체의 상태 변경 시 모든 불변성을 강제할 수 있게 합니다.”
예를 들어, “주문 애그리거트”는 addProduct(), removeProduct(), confirmOrder()와 같은 메서드를 정의할 수 있습니다. 이 경우 “주문 애그리거트”는 제품을 참조하며, 이 관계는 제품 ID를 사용하여 관리됩니다.
리포지토리 (Repository)
리포지토리는 일반적으로 안전하게 보관하거나 저장된 항목을 보호하는 장소를 의미합니다. 리포지토리에 무언가를 저장하고 나중에 다시 가져올 때, 그것이 저장했을 때와 동일한 상태일 것이라고 기대합니다. 때로는 리포지토리에서 저장된 항목을 제거하기도 합니다.
리포지토리는 도메인 모델과 데이터 저장 메커니즘(데이터베이스, 웹 서비스 또는 다른 데이터 소스) 사이의 추상화 계층 역할을 합니다. 리포지토리는 도메인 객체에 대한 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 수행하는 메서드를 제공하며, 이러한 작업이 저장 시스템에서 어떻게 수행되는지에 대한 구현 세부 사항을 숨깁니다.
예시:
다음 예시에서는 동일한 “호텔 시스템”을 사용하겠습니다. 도메인 표현에서 리포지토리는 “호텔 예약” 컨텍스트의 일부로, 컨텍스트에 정의된 엔티티에 대한 CRUD 작업을 수행합니다.
호텔 시스템
-- 컨텍스트: 호텔 예약
-- 엔티티
-- 값 객체
-- 서비스
-- 리포지토리
-- findAll()
-- save()
-- update()
-- findByID()
-- remove()
-- 애그리거트
엔티티, 값 객체, 애그리거트, 서비스 간의 관계
다음 장으로 넘어가기 전에 엔티티, 값 객체, 애그리거트, 서비스 간의 관계에 대해 간략히 설명드리겠습니다. 이는 시스템 내에서 응집력과 최적의 결합도를 유지하는 데 도움이 됩니다. 다음 단락에서 이 개념들 간의 관계를 설명하겠습니다.
엔티티와 값 객체 간의 관계
엔티티는 도메인 내에서 고유하고 식별 가능한 객체를 나타내며, 고유한 정체성을 가지고 시간이 지나면서 상태가 변할 수 있습니다. 반면, 값 객체는 도메인 내에서 고유한 정체성을 가지지 않는 불변의 개념을 나타내며, 속성과 특성으로 정의됩니다.
엔티티는 특정 속성이나 복잡한 특성을 표현하기 위해 값 객체를 내부 구조의 일부로 포함할 수 있습니다.
예시:
이커머스 시스템을 상상해 보세요. “제품” 엔티티는 각 카테고리와 관련된 제품 특성을 저장하는 “카테고리” 값 객체를 가질 수 있습니다. 이 값 객체는 “카테고리 이름”, “카테고리 설명” 등을 정의할 수 있습니다.
애그리거트와 엔티티/값 객체 간의 관계
애그리거트는 일관성을 유지하면서 트랜잭션 관점에서 함께 그룹화된 일련의 엔티티와 값 객체를 의미합니다. 애그리거트 루트는 관련된 엔티티와 값 객체에 접근하고 조작하는 진입점 역할을 합니다.
예시:
이커머스 시스템을 상상해 보세요. “쇼핑 카트” 애그리거트를 정의했다고 가정하면, 이 애그리거트는 “제품” 엔티티와 “배송 주소” 또는 “카테고리” 값 객체를 포함할 수 있습니다.
서비스와 애그리거트 간의 관계
서비스는 고수준의 작업을 수행하며, 도메인 내에서 애그리거트 또는 엔티티 간의 상호작용을 조정하는 역할을 합니다. 서비스와 애그리거트 간의 관계는 다음과 같습니다:
- 서비스는 애그리거트와 그 엔티티를 사용하여 여러 도메인 객체와 관련된 복잡한 작업을 수행합니다.
- 서비스는 단일 애그리거트에 속하지 않는 비즈니스 로직을 캡슐화하여, 경계 컨텍스트를 넘어서는 작업을 수행할 수 있게 합니다.
예시:
이커머스 시스템을 예로 들면, “주문 관리자” 서비스는 “주문 생성”과 같은 작업을 수행하며, 애그리거트와 “제품” 엔티티를 사용하여 주문의 생성과 확인을 조정할 수 있습니다.
핵심 분리 (Core Segregation)
핵심 분리(Core Segregation)는 시스템의 핵심 도메인을 다른 요소들로부터 분리하고 격리하는 방법을 의미합니다. 이를 통해 복잡성을 줄이고 모듈화된 설계를 촉진하여 특정 도메인과 그 비즈니스 규칙에 집중할 수 있게 합니다. 이는 시스템의 이해, 유지보수 및 발전을 용이하게 합니다.
이 방법은 프로젝트 초기 단계부터 시스템의 설계와 아키텍처에 적용해야 합니다. 언제 이를 구현할 수 있는지, 또는 구현을 위해 어떤 측면을 고려해야 하는지 궁금하실 것입니다. 다음은 핵심 분리를 적용할 수 있는 시기를 안내하는 몇 가지 요소입니다:
- 비즈니스 규칙이 풍부한 복잡한 도메인이 있을 때
- 다학제 팀과 협력할 때
- 시스템이 발전하고 성장할 것으로 예상될 때
이러한 요소들은 도메인을 분리할 좋은 시기를 판단하는 데 도움이 됩니다.
핵심 분리는 시스템 내에서 도메인과 서브도메인을 식별하는 데 도움을 줍니다.
부패 방지 계층 (Anti-corruption Layer)
간단히 말해, DDD에서 부패 방지 계층(ACL)은 도메인 코어를 외부 영향으로부터 보호하는 패턴입니다. 주로 DDD 기반 시스템을 기존 레거시 시스템, 외부 서비스 또는 복잡한 기술 구성 요소와 통합할 때 사용됩니다. 이 레이어의 목적은 도메인 코어의 언어와 비즈니스 규칙을 외부 시스템과의 상호작용 방식과 조정하는 데 있습니다.
부패 방지 계층은 도메인 코어와 외부 시스템 또는 구성 요소 사이에 위치하며, 도메인 모델과 비즈니스 규칙에 일관되도록 외부 시스템과의 상호작용을 번역하거나 조정하는 역할을 합니다.
몇 가지 주요 측면은 다음과 같습니다:
모델 변환
부패 방지 계층은 핵심 도메인(DDD 시스템)의 개념과 데이터를 외부 시스템이 사용하는 용어와 구조로 매핑하고 변환하는 책임을 집니다. 이를 통해 핵심 도메인이 외부 시스템과 효율적이고 일관되게 통신할 수 있습니다.
인터페이스 적응
부패 방지 계층은 외부 시스템이 사용하는 인터페이스와 프로토콜을 도메인 모델과 일치하도록 조정할 수 있습니다. 여기에는 어댑터, 변환기 또는 데이터 변환 작업이 포함될 수 있습니다
핵심 도메인 보호
모델과 비즈니스 규칙을 보호하는 격리 계층을 제공하여 이들이 독립적으로 발전할 수 있도록 합니다. 외부 시스템의 복잡성이나 제한이 핵심 도메인에 직접 영향을 미치지 않도록 방지하는 장벽을 정의합니다.
트랜잭션 계층 (Transaction Layer)
도메인 주도 설계(DDD)에서 트랜잭션 계층은 데이터베이스의 읽기 및 쓰기 작업을 관리하며, 트랜잭션 동안 데이터의 일관성과 무결성을 보장하는 소프트웨어 아키텍처의 한 계층을 의미합니다.
이 계층의 목적은 작업이 원자적으로 수행되도록 보장하는 것입니다. 즉, 트랜잭션 내의 모든 작업이 완료되거나 아니면 아무 작업도 수행되지 않도록 하여 불일치 상태나 데이터 손상을 방지합니다. 다시 말해, 트랜잭션이 성공하려면 모든 작업이 성공해야 하며, 하나의 작업이라도 실패하면 트랜잭션은 실패한 것으로 간주되어 변경 사항은 적용되지 않습니다.
바운디드 컨텍스트 통합
바운디드 컨텍스트 통합은 동일한 시스템 내에서 서로 다른 바운디드 컨텍스트 간의 커뮤니케이션 필요성을 의미합니다.
복잡한 시스템을 작업할 때, 여러 서브도메인이나 책임 영역을 다루기 위해 여러 바운디드 컨텍스트가 필요하게 됩니다. 이때 서로 다른 바운디드 컨텍스트 간의 협력을 위해 통합이 필요합니다.
각 바운디드 컨텍스트는 자율적이고 명확한 경계를 가지고 있지만, 정보와 이벤트를 교환하고 협력하기 위한 메커니즘을 마련하는 것이 중요합니다.
바운디드 컨텍스트를 통합하는 방법으로는 REST API, 이벤트, 메시징 시스템 등의 인터페이스를 사용하는 것이 있으며, 도메인 이벤트, PUB/SUB 패턴 등의 통합 패턴을 구현하는 방법도 있습니다.
바운디드 컨텍스트의 통합은 독립적인 개발과 다양한 컨텍스트의 발전을 가능하게 하면서도 시스템 전체의 협업과 일관성을 보장하여, 확장 가능하고 복잡한 DDD 시스템을 구축하는 데 필수적입니다.
마무리
보시다시피 도메인 주도 설계(DDD)는 이해하기 어렵지 않지만, 매우 방대한 주제입니다. 이 글에서는 몇 가지 주요 사항을 다루었지만, 더 깊이 배우고 싶다면 DDD 창시자인 에릭 에반스의 책을 읽어보시길 권합니다. 책을 통해 DDD에 대해 더 깊이 배울 수 있습니다.
이 글의 간략한 설명이 DDD에 대한 기본적인 지식을 제공할 수 있으며, 관련 강의를 듣거나 다른 DDD 관련 글을 읽으시면 더욱 빠르게 이해할 수 있을 것입니다. 최범균 님의 “도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지“도 DDD를 처음 이해하는 데 큰 도움이 됩니다.
DDD를 익히는데 제가 추천하는 한 가지 연습 방법은 현재 작업 중인 프로젝트를 분석하고, 이 글에서 논의한 각 점을 실제로 개발해보는 것입니다. 이를 통해 각 개념을 더 잘 이해할 수 있을 것입니다.
긴 글을 끝까지 읽어주셔서 정말 감사합니다. 꼭, 도움이 되셨으면 좋겠습니다.
궁금하신 사항은 편하게 댓글로 남겨주세요 ~!! 🙂