나에게 도메인 주도 설계는
설계는 단지 어떻게 생겼는지, 어떤 느낌인지가 아니라, 그게 어떻게 동작하는지에 대한 것이다. - 스티브 잡스
전략적 설계
- 비즈니스 상 전략적으로 중요한 것
- 중요도에 따라 일을 나누는 방법
- 필요에 따라 통합하는 최적의 방법
- Bounded Context를 사용해서 도메인 모델을 분리하는 방법
- Bounded Context 안의 도메인 모델에서 보편언어를 개발하는 방법
- Subdomains이 무엇이고
- 어떻게 Subdomains이 기존 시스템의 제한되지 않은 복잡성을 다룰 수 있게 도와주는지
- 앞으로 진행할 프로젝트 결과를 어떻게 향상시킬 수 있는지
- Context Mapping이라는 기술을 통해 여러 개의 Bounded Context를 통합하는 방법
- Context Map은 2개의 Bounded Context를 통합하면서 그 사이에 존재하는 팀의 관계, 기술적 메커니즘을 정의한다.
전술적 설계
- Entity와 Value Object를 알맞은 크기의 Aggregate으로 묶는데 사용하는 Aggregate 패턴
- Domain Events의 사용은 명확하게 모델링하는 것을 도와주면서, 도메인에 발생한 것에 대해 알아야 하는 내용을 시스템과 공유하는 것을 돕는다.
- 공유할 대상이 로컬의 Bounded Context일 수도, 다른 원격의 Bounded Context일 수도 있다.
Bounded Context 및 보편언어와 전략적 설계
DDD는 주로 명확하게 Bounded Context 내에서 보편언어를 모델링하는 것에 대한 것이다.
Bounded Context
- 모델이 구현되는 곳으로 특정한 의미를 갖고, 특정한 일을 수행한다.
- Bounded Context 마다 각각 분리된 소프트웨어 산출물이 나온다.
보편언어
- 도메인 전문가와 개발자가 공통으로 사용하는 언어
- 엄격, 정확, 엄중, 단호해야 한다.
핵심 도메인
- 조직의 핵심 전략적 계획으로 개발되고 있는 Bounded Context
- 무엇이 핵심 도메인이어야 하고, 어떤 것을 제외시켜야 하는지 현명하게 선택해야 한다.
중요한 위험 요소
너무 많은 것을 하나의 모델에 넣는 것과 큰 진흙 덩어리를 만드는 것
- 시스템의 명확한 경계 없이 여러 개의 뒤엉킨 모델들을 담고 있다.
- 서로 관련이 없는 다양한 개념들이 수많은 모듈로 확장
- 어울리지 않는 모듈을 상호 연계
- 테스트를 수행하는데 아주 오랜 시간이 걸림
DDD 전략적 설계의 적용
- 서로 다른 개념들을 각기 다른 Bounded Context 안으로 분리해 놓음으로써 개념 간 차이를 중시한다.
- '정책'에 3가지 의미가 존재한다면, 3개의 Bounded Context들은 각각 고유한 정책적 특성을 포함한 정책을 갖는다.
Bounded Context와 보편언어의 사용
- Bounded Context
- '핵심이 무엇인가?'라는 질문을 계속 던져보자.
- 핵심이 되는 개념들은 팀이 사용하는 보편언어의 일부가 된다.
- 나머지 개념들은 모두 제외시켜야 한다.
- 핵심 도메인에서 제외시켰던 다른 모델링 개념들 중 몇몇은 각각의 Bounded Context 내에 정의될 가능성이 높고, 각각의 보편언어에 연결될 것이다.
- Context Mapping을 이용해 이들을 통합한다.
Bounded Context 내에서 발견할 수 있는 아키텍처 컴포넌트
- Bounded Context는 도메인 모델 이상의 다양한 요소들로 구성된다.
- 예: 사용자 인터페이스 컨트롤러, REST endpoints, message listeners와 같은 Input Adapters, 유스케이스를 조율하고 트랜잭션을 관리하는 Application services, ...
서브도메인과 전략적 설계
서브도메인
서브도메인이란
- 전체 비즈니스 도메인의 하위 부분
- 하나의 논리적 도메인 모델을 나타내는 것
- 전체 비즈니스 도메인을 논리적으로 쪼개는 데 서브도메인을 사용할 수 있다.
서브도메인의 유형
- 핵심 도메인
- 보편언어를 신중하게 만들기 위한 전략적 투자 영역
- 주요 자원을 할당하는 명시적인 Bounded Context 이다.
- 지원 서브도메인
- 이미 존재하는 제품으로 해결할 수 없는 맞춤 제작 개발이 필요한 모델링 영역을 말한다.
- 아웃소싱을 고려해볼 수도 있다.
- 일반 서브도메인
- 기존 제품 구매를 통해 바로 충족 시킬 수 있는 경우에 해당한다.
- 아웃소싱을 할 수도 있고, 직접 개발할 수도 있다.
- 일반 서브도메인을 핵심 도메인으로 오해하지 않도록 주의하자.
Bounded Context와 서브도메인을 1:1 관계로 맺자
- Bounded Context를 정확하게 유지시키고 핵심 전략 목표에 집중하는 데 도움을 준다.
핵심 도메인과 지원 서브도메인을 2개의 서로 다른 Bounded Context로 분리하는 것이 현실적이지 않을 때, DDD 모듈로 그것을 분리하자
컨텍스트 매핑과 전략적 설계
컨텍스트 매핑의 종류
파트너십
- 두 팀이 함께 성공하거나 다같이 실패한다.
- 상호간 의존적인 작업이나 여러 일정들을 조율하고, 통합을 적절하게 유지하기 위해 지속적으로 통합에 노력한다.
고객-공급자
- 2개의 Bounded Context와 각 팀들의 관계를 나타낸다.
- 고객이 언제 무엇을 받게 될지는 결국 공급자가 정한다.
준수자
- 하류 팀이 현재의 상류팀 모델을 그대로 따른다.
- 예: 아마존과 제휴하는 판매자 중 하나가 아마존 시스템과 통합하려고 할 때 아마존 모델을 준수한다.
반부패 계층
- 가장 방어적인 컨텍스트 매핑 관계이다.
- 하류 팀이 그들의 보편언어 모델과 상류 팀의 보편언어 모델 사이에 번역 계층을 만드는 것이다.
- 이 계층은 상류 모델로부터 하류 모델을 독립시키고 둘 사이를 번역한다.
공개 호스트 서비스
- 일련의 서비스처럼 Bounded Context에 대한 접근을 제공하는 프로토콜이나 인터페이스를 정의한다.
- 보통 서드파티에게 공표된 언어를 제공한다.
공표된 언어
- 이를 사용하는 Bounded Context의 규모에 관계없이, 모두 간단한 사용과 번역을 가능하게 하는 정보 교환 언어이다.
- XML 스키마, JSON 스키마처럼 좀 더 최적화된 작성 형식으로 정의할 수 있다.
- 두 보편언어 사이에 번역을 제공한다.
컨텍스트 매핑 통합 방법
SOAP을 이용한 RPC
- 원격 프로시저 호출인 RPC는 다양한 방법으로 동작한다.
- RPC의 잘 알려진 사용법 중 하나는 SOAP을 이용하는 방법이다.
- RPC 사용의 문제점
- 네트워크나 SOAP API를 호스팅하는 시스템에 문제가 생기면 호출은 실패한다.
- SOAP을 이용한 RPC는 클라이언트 Bounded Context와 서비스를 제공하는 Bounded Context 사이의 강한 결합을 암시한다.
- 원치 않는 외부의 영향으로부터 클라이언트 Bounded Context를 분리할 필요가 있다면, 반부패 계층을 정의하자.
RESTful HTTP
- Bounded Context 간에 교환되는 리소스 뿐만 아니라 POST, GET, PUT, DELETE 네 가지 주요 오퍼레이션들이 관여된다.
- 공표된 언어로 리소스를 정의하고 REST URI로 구성하면 공개 호스트 서비스를 구성할 수 있다.
- 도메인 모델 안에 직접적으로 Aggregate을 반영하는 리소스를 설계하지 말자.
- 실제 있는 그대로의 도메인 모델이 아니라 클라이언트에게 제공하는 리소스가 그들이 원하는 것에 대한 구성과 형태를 갖도록 고려해야 한다.
- 클라이언트가 원하는 것은 모델의 지금 현재 구성이 아니라 리소스의 설계를 활용하는 것이다.
메시징
- RPC나 REST와 달리, 분절된 형태와의 일시적인 결합을 대부분 제거할 수 있다.
- 메시지 교환에서는 지연 가능성이 있기 때문에 즉각적인 결과가 필수 적이지 않을 때 사용하면 좋다.
- 구독 Bounded Context가 도메인 이벤트를 받으면, 이벤트 형태와 값을 토대로 동작을 수행한다.
- 특정한 수행을 위해 발행 Bounded Context에 커맨드 메시지를 보낼 수도 있다.
- 구독 Bounded Context는 멱등 수신자로 구현되야 한다.
- 메시징으로 설계하면 전체 솔루션을 매우 견고하게 만들 수 있다.
컨텍스트 매핑 구축
사례
- '계약 심사' 컨텍스트에서 '정책' 컴포넌트가 만들어진다.
- 정책 발행이라는 이름의 도메인 이벤트를 발생시킬 수 있다.
- 이 이벤트는 정책 ID를 통해 정책들을 식별한다.
- 메시징 구독을 통해 이를 제공받은 구독 Bounded Context 안에 이 정책에 상응하는 '정책' 컴포넌트를 만들 수 있다.
- 구독 Bounded Context에 생성된 모든 컴포넌트는 발신 주체인 '계약 심사' 컨텍스트로의 역추적을 위해 정책 ID(식별자)를 보유한다.
- 이제 '정책' 발행 ID로 쿼리를 수행하여 언제든지 '계약 심사' 컨텍스트로부터 더 많은 정보를 가져올 수 있다.
모두 담는 것과 다시 쿼리하는 것 사이의 장단점
- 모두 담는 것
- 소비자들에게 큰 자율성을 허용
- 보안적인 측면이 부실해질 수 있다.
- 다시 쿼리하는 것
- 가벼운 도메인 이벤트
- 소비자들이 높은 보안 수준에서 요청할 수 있다.
Aggregate과 전술적 설계
Entity
- 독립적인 것
- 각 Entity는 같은 형태를 띠거나 다른 형태의 Entity들과의 특성을 구별할 수 있는 고유한 식별성을 갖는다.
- 변할 수 있는 것이며, 상태는 계속해서 변할 수 있다.
Aggregate
- 1개 이상의 Entity로 구성된다.
- 그중 한 Entity는 Aggregate 루트라고 부른다.
- 루트 Entity는 Aggregate 안의 다른 모든 요소를 소유한다.
- 루트 Entity의 명칭은 Aggregate의 개념적 명칭이다.
- 구성에 Value Object를 포함할 수 있다.
Value Object
- 불변의 개념적 완전성을 모델링한 것이다.
- Entity와 달리 고유한 식별성이 없다.
- 값 형태로 캡슐화 된 속성을 비교함으로써 동일함이 결정된다.
- Value Object가 어떤 것을 나타낸다기보다는 Entity를 서술하고, 수량화하거나 측정하는데 사용된다.
Aggregate의 특징
- 각 Aggregate은 일관성 있는 트랜잭션 경계를 형성한다.
- 트랜잭션은 Aggregate에 대한 변경을 독립시키고, 비즈니스 불변성을 각 비즈니스 오퍼레이션에 맞게 일관성을 보장하는 방법이다.
- Aggregate의 상태나 이벤트 소싱은 항상 안전하고 정확하게 트랜잭션으로 처리하고 관리해야 한다.
Aggregate 설계의 기본 규칙
- Aggregate 경계 내에서 비즈니스 불변사항들을 보호하라.
- 작은 Aggregate을 설계하라.
- 각 Aggregate의 메모리 사용량과 트랜잭션 범위가 비교적 작아야 한다.
- 빠르게 로드되고 가비지 컬렉션도 더 빨라지며 테스트도 쉬워진다.
- SRP를 따르는지 항상 생각하자.
- 오직 ID를 통해 다른 Aggregate을 참고하라.
- 동일한 트랜잭션 내에 다른 Aggregate을 수정하지 않게 해준다.
- 여러 형태의 저장 메커니즘 모두 쉽게 저장할 수 있다.
- 결과적 일관성을 사용해 다른 Aggregate을 갱신하라.
- 결과적 일관성이란?
- 분산 시스템에서 데이터를 조회할 때 모든 시스템이 동일한 데이터를 가질 수 있다고 보장할 수는 없다.
- 결과적 일관성은 어느 시점에는 데이터가 다를 수 있지만, 결국에는 모든 시스템이 최신의 데이터를 가질 수 있도록 보장된다는 내용이다.
- Aggregate의 트랜잭션의 일부로 구독한 Bounded Context들에게 도메인 이벤트를 발행시킬 수 있다.
- 도메인 이벤트를 발행한 컨텍스트가 본인을 구독할 수도 있다.
- 결과적 일관성이란?
Aggregate 모델링
- 비즈니스 로직은 도메인 모델 안에서 정의해야 한다.
- Aggregate의 모든 부분은 보편언어에 따라 모델링해야 한다.
- 모델링하는 각 개념마다 적절한 수준의 추상화를 선택하자.
- 올바른 크기의 Aggregate(일관성 경계 목표에 도달하는 설계 단계)
- 작은 Aggregate을 설계하라.
- Aggregate 경계 내에서 비즈니스 불변사항들을 보호하라.
- 반응에 맞춘 갱신이 일어나는 시간은 얼마나 걸릴지 도메인 전문가에게 확인하자.
- 각각의 Aggregate이 즉시 처리돼야 할 경우, 동일한 Aggregate 경계 안에 그 2개의 Entity를 구성하는 것을 긍정적으로 검토해야 한다.
- 각각의 Aggregate이 주어진 시간에 따라 각각 반응하는 경우, 결과적 일관성을 사용해 다른 Aggregate을 갱신하라.
- 단위 테스트를 위해 Aggregate을 철저하게 캡술화되도록 설계하자.
도메인 이벤트와 전술적 설계
도메인 이벤트 설계, 구현, 사용하기
도메인 이벤트를 생성, 이름 붙이기
- 도메인 모델의 보편언어를 반영해야 한다.
- 도메인 이벤트에 제대로 이름 붙이는 것은 굉장히 중요하다.
- 도메인 이벤트 타입을 나타내는 이름
- 과거형 동사로 표현하자.
- 예: ProductCreated, SprintScheduled
표준 도메인 이벤트 인터페이스를 정의하고 구현
// 모든 **도메인 이벤트**가 반드시 지원해야 하는 최소한의 인터페이스 // 일반적으로 **도메인 이벤트**가 발생할 때 그 날짜와 시각을 전달하길 원하는데, 이를 위해 OccurredOn 프로퍼티를 제공한다. public interface DomainEvent { public Date OccurredOn { get; } }
도메인 이벤트의 프로퍼티를 정의하는 방법
- '애플리케이션에서 어떤 것들이 도메인 이벤트를 발생시킬까?' 질문해보자.
- 도메인 이벤트 타입은 이벤트가 만들어지는 시점에, 명령이 제공하는 모든 프로퍼티들을 담고 있어야 한다.
이벤트 리파지토리에 도메인 이벤트를 저장하는 방법
- 이벤트 리파지토리란 ?
- 모든 도메인 이벤트를 추가하는 순차적인 리파지토리 컬렉션 또는 테이블
- 오직 추가만 가능해서 리파지토리 메커니즘은 매우 빠르게 동작한다.
- 성능을 고려한다면 캐싱과 스탭샷에 대해 알고 있는 것이 좋다.
- Aggregate을 하나의 테이블에 그리고 도메인 이벤트를 입엔트 리파지토리 테이블에 저장하고 난 후, 트랜잭션을 설정할 수 있다.
- 이벤트 소싱을 사용한다면, Aggregate의 상태는 도메인 이벤트 자체로 온전히 표현할 수 있다.
- 도메인 이벤트를 이벤트 리파지토리에 유지하는 것은 도메인 모델 간에 발생한 것에 대한 인과관계의 순서를 지속시켜준다.
도메인 이벤트 저장 후 이를 발생시키는 방법
- 일부 도메인 이벤트는 명령에 의해 유발될 수 있고, 다른 경우에는 일자, 시간과 같은 상태가 변경되는 것들의 인지를 통해 유발될 수 있다.
이벤트 소싱
- Aggregate 인스턴스에 대해 변경된 것에 대한 기록으로, 발생했던 모든 도메인 이벤트를 저장하는 것을 말한다.
- 즉, Aggregate 상태 전체를 저장하는 대신, 발생했던 각 도메인 이벤트 모두를 저장한다.
- 큰 이점 중 하나는 핵심 도메인에서 계속 발생하는 모든 기록을 개별적인 발생 수준으로 저장한다는 점이다.
- 법적 기준에 대한 준수 및 분석이 가능
- 디버깅하거나 이벤트 사용 추세를 조사할 수 있는 등 기술적인 이점이 있다.
'OOP | DDD' 카테고리의 다른 글
[객체지향의 사실과 오해] 객체지향을 제대로 이해하고 사용하자 ! (0) | 2020.02.24 |
---|