[도메인 주도 설계 핵심] 4장 요약

컨텍스트 매핑과 전략적 설계

DDD 프로젝트에는 핵심 도메인 뿐만 아니라 프로젝트와 관련된 여러 개의 바운디드 컨텍스트가 있다.

핵심 도메인인 애자일 프로젝트 관리 컨텍스트에 맞지 않는 다른 개념들은 전부 다른 바운디드 컨텍스트 중 하나로 옮겨서 관리한다.

 

이때, 애자일 프로젝트 관리 핵심 도메인을 다른 바운디드 컨텍스트와 통합해야 하는데, DDD에서는 이런 통합을 컨텍스트 매핑이라고 한다. 이전의 컨텍스트 맵에서 Discussion이 2개의 서로 다른 바운디드 컨텍스트 내에 존재하는 것을 확인할 수 있고, 애자일 프로젝트 관리 컨텍스트가 Discussion의 소비 주체라는 점과 협업 컨텍스트가 Discussion의 근원이라는 것도 기억해야 한다.

두 바운디드 컨텍스트 안에 각각의 보편언어가 있는 것을 생각해보면 은 두 언어 사이의 통역을 나타낸다.

 

컨텍스트 매핑을 살펴볼 때, 2개의 바운디드 컨텍스트 사이에 놓여진 선이 어떤 종류의 팀들 사이에 존재하는 관계와 통합인지에 주목해야 한다. 두 영역 간 경계와 그 사이의 관계를 잘 정의한다면 시간에 따라 적절한 조정을 통해 변화를 지원할 수 있다.

 


매핑의 종류

1) 파트너십

두 팀 사이에 파트너십 관계가 존재할 수 있다. 각 팀은 하나의 바운디드 컨텍스트를 책임진다.

두 팀은 일련의 목표에 대한 의존성을 맞추기 위해 파트너십을 구성한다. (같이 성공하거나 같이 실패한다는 뜻)

 

2) 공유 커널

두 바운디드 컨텍스트의 교차 지점으로 표시한 공유 커널(Shared Kernel)은 2개 이상의 팀 사이에 작지만 공통인 모델을 공유하는 관계를 나타낸다. 각 팀은 공유하는 모델 요소에 대해 서로 합의해야 한다.

공유하는 모델의 코드, 빌드를 관리하고 테스트하는 것은 한 팀에 맡아 수행한다. 관련된 모든 팀들이 각자 저마다의 길을 가는 것보다 공유 커널이 더 좋은 생각이라고 여긴다면 좋은 결과를 얻을 수 있다.

 

3) 고객-공급자

고객-공급자는 2개의 바운디드 컨텍스트와 각 팀들간의 관계를 나타낸다.

공급자는 상류(Upstream, U), 고객은 하류(Downstream, D)로 표현한다.

공급자는 고객이 원하는 것을 제공해야 하므로 관계를 주도하는 것은 공급자다. 

 

다양한 기대를 충족시키기 위해 공급자와 함께 계획하는 것은 고객의 역할이지만 고객이 언제 무엇을 받게 될지는 결국 공급자가 결정한다.

 

4) 준수자

준수자 관계는 상류와 하류 팀이 있고, 상류 팀이 하류 팀의 특정 요구에 지원할 동기가 없는 경우에 나타난다. 

이런 상황에서는 하류팀이 자기들의 특정 요구에 맞춰 상류 모델의 변환시키는 것은 쉽지 않기 때문에 현재의 상류팀 모델을 그대로 따른다.

확실하게 자리잡은 매우 거대하고 복잡한 모델과의 통합이 필요할 때 주로 사용된다.

예시) 아마존과 제휴하는 판매자가 아마존 시스템과 통합하려고 할 때, 아마존 모델을 준수하는 것

5) 반부패 계층

가장 방어적인 컨텍스트 매핑 관계이며, 하류 팀이 그들의 보편언어 모델과 상류 팀의 보편언어 모델 사이에 번역 계층을 만드는 것.

이 계층은 상류 모델로부터 하류 모델을 독립시키고 둘 사이를 번역한다.

 

가능하다면 하류 모델과 상류 통합 모델 사이에 반부패 계층을 만들어야 한다. 이렇게 함으로써 통합에 용이한 모델 개념을 만들고 원하는 형태의 특정 비즈니스 요구도 맞추고 외부의 이질적인 개념으로부터 독립성을 유지시킬 수도 있다.

 

6) 공개 호스트 서비스

바운디드 컨텍스트에 대한 접근을 제공하는 프로토콜이나 인터페이스를 정의한다. 프로토콜은 우리의 바운디드 컨텍스트와 통합하고자 하는 모두가 용이하게 사용할 수 있도록 공개되어 있다. 

애플리케이션 프로그래밍 인터페이스(API)로 제공하는 서비스는 문서화가 잘되어 있고, 사용하기가 좋다.

 

7) 공표된 언어

위 그림에 표시되어 있는 공표된 언어는 이를 사용하는 바운디드 컨텍스트의 규모에 관계없이 모두 간단한 사용과 번역을 가능하게 하는 잘 문서화된 정보 교환 언어이다. 공표된 언어는 XML 스키마, JSON 스키마 처럼 좀 더 최적화된 작성 형식으로 정의할 수 있다.

보통 공개 호스트 서비스는 서드파티에게 최상의 통합 경험을 줄 수 있는 공표된 언어를 제공한다. 이 결합은 매우 펴니하게 두 보편언어 사이에 번역을 제공한다.

 

8) 각자의 길

1개 이상의 바운디드 컨텍스트를 통합으로 다양한 보편언어를 사용하는 것이 유의미한 결과를 제공하지 못하는 상황을 말한다. 

즉, 우리가 원하는 기능을 다른 보편언어에서 완전히 제공하지 않는 경우이며, 이런 경우에는 우리의 바운디드 컨텍스트 내에서 이를 위한 해결 방안을 만들고 통합은 잊어야 한다.

 

9) 큰 진흙 덩어리

큰 진흙 덩어리를 만드는 것은 피해야 한다.

 

진흙 덩어리를 만들고 있다면 어떤 부작용이 생길지 알아보자

(1) 부적절한 연결과 의존으로 인해 문제를 확산시키는 애그리게잇이 증가한다.

(2) 큰 진흙 덩어리의 일부에 문제가 생겨서 이를 관리할 때는 한가지 문제를 해결해도, 다른 문제를 야기시킬 수 있다.

(3) 전반적 지식과 모든 언어를 한 번에 다룰 수 있는 사람이 있어야 시스템을 완전한 붕괴로부터 지킬 수 있다.

 

이미 수많은 진흙 덩어리가 있는데 우리가 외부의 진흙 덩어리들과 통합해야한다면 레거시 시스템에 대응한 반부패 계층을 만들어서 형편없는 결과로부터 우리의 모델을 보호해야 한다.


컨텍스트 매핑 활용하기

우리에게 주어진 바운디드 컨텍스트와 통합할 때, 어떤 종류의 인터페이스를 제공받을 수 있을까? 

이는 해당 바운디드 컨텍스트를 소유한 팀에 달려있는데 크게 4가지가 존재한다.

1. SOAP(Simple Object Access Protocol)을 이용한 RPC

2. RESTful 인터페이스

3. 큐와 발행-구독 모델을 사용하는 인터페이스 메시징

4. 데이터베이스나 파일 시스템의 통합 ➡️ 이는 피해야하지만 어쩔 수 없다면 반부패 계층을 통해 모델을 독립시켜야 한다.

 

1) SOAP를 이용한 RPC(원격 프로시저 호출, Remote Procedure Calls)

RPC의 잘 알려진 사용법 중 하나는 SOAP을 이용하는 방법이며, SOAP을 이용한 RPC는 다른 시스템이 서비스를 사용할 때 마치 단순히 로컬 프로시저나 메서드를 호출하는 것처럼 사용한다는 개념이다.

SOAP을 이용한 RPC는 클라이언트 바운디드 컨텍스트와 서비스를 제공하는 바운디드 컨텍스트 사이의 강한 결합을 암시한다.

 

SOAP이나 다른 방식으로 RPC를 사용하는 것의 주된 문제점은 견고함이 떨어질 수 있다는 것이다. 네트워크나 SOAP API를 호스팅하는 시스템에 문제가 생기면 간단한 프로시저 호출은 에러 결과만 받은채 완전히 실패한다.

RPC가 제대로 동작하기만 한다면 통합하기에 좋은 방법일 수 있다. 

원치 않는 외부의 영향으로부터 클라이언트의 바운디드 컨텍스트를 분리할 필요가 있다면 반부패 계층을 정의하자.

 

 

2) RESTful HTTP

RESTful HTTP를 사용한 통합은 바운디드 컨텍스트 간에 교환되는 리소스뿐만 아니라 POST, GET, PUT, DELETE 4가지 주요 오퍼레이션들이 관여된다. 이 방식은 분산 컴퓨팅에 적합한 API를 정의하는데 도움을 준다.

REST 인터페이스를 제공하는 서비스 바운디드 컨텍스트는 공개 호스트 서비스와 공표된 언어를 제공해야 한다. 

공표된 언어로 리소스를 정의하고, REST URI로 구성하면 온전한 공개 호스트 서비스를 구성할 수 있다.

RESTFul API도 네트워크, 서비스 제공자의 장애 등의 이유로 실패할 수 있다.

 

REST를 사용할 때, 저지르는 흔한 실수는 도메인 모델 내부에 직접적으로 애그리게잇을 반영하는 리소스를 설계하는 것이며, 이렇게 하면 모든 클라이언트에게 준수자 관계를 강요하면서 모델 변화가 리소스의 형태에 영향을 준다. 

대신, 리소스가 클라이언트 주도의 유스케이스를 지원할 수 있도록 종합적으로 설계해야 한다. 

➡️ 종합적이라는 것은 있는 그대로의 도메인 모델이 아닌 클라이언트에게 제공하는 리소스가 그들이 원하는 것에 대한 구성과 형태를 갖도록 고려해야 한다는 의미다. 

 

중요한 것은 클라이언트가 원하는 것은 모델의 지금 현재 구성이 아닌 리소스의 설계를 활용하는 것이다.

 

3) 메시징

 

통합에 비동기 메시징을 사용하면 우리의 바운디드 컨텍스트 또는 다른 바운디드 컨텍스트가 발행하는 도메인 이벤트를 구독하는 클라이언트 바운디드 컨텍스트가 많은 것을 할 수 있게 해준다.

메시징을 사용한 통합은 가장 견고한 형태 중 하나이다. 왜냐하면 RPC, REST와 달리 분절된 형태와의 일시적인 결합을 대부분 제거할 수 있기 때문이다. 즉, 메시징은 더 느슨한 결합(loose coupling)을 제공한다는 의미이며 이를 통해 시스템 간의 의존성이 줄어들고, 안정성 및 유연성이 증가하게 된다.

 

REST를 사용해 비동기하기
증가하는 리소스들에 대해 REST 기반으로 폴링(polling)하는 방식으로 비동기 메시징을 구현해 처리할 수도 있다.

 

 

일반적으로 바운디드 컨텍스트 내의 애그리게잇은 도메인 이벤트를 만들 때 관심 있는 다른 컨텍스트들은 발생된 이벤트를 사용한다.

즉, 구독 바운디드 컨텍스트가 도메인 이벤트를 받으면 이벤트의 형태와 값을 토대로 동작을 수행한다. 이때 소비하는 바운디드 컨텍스트 안에 새로운 애그리게잇을 생성하거나 기존 애그리게잇을 수정해야 할 때가 있다.

이는 구독 바운디드 컨텍스트의 요청 없이 발행 바운디드 컨텍스트에서 발생하는 일도 구독 바운디드 컨텍스트에게 이득이 된다는 가정이다.

 

 

그러나 클라이언트 바운디드 컨텍스트가 서비스 바운디드 컨텍스트에게 특정한 수행을 하도록 커맨드 메시지를 보낼 필요가 있는데, 이 경우에도 클라이언트 바운디드 컨택스트는 발행된 도메인 이벤트 형태로 결과를 받는다.

 

통합에 메시징을 사용하는 모든 경우, 전체 솔루션의 품질은 사용한 메시징 메커니즘의 품질에 크게 좌우된다. 메시징 메커니즘은 적어도 한 번의 전달을 통해 모든 메시지의 수신을 보장해야 하며 구독 바운디드 컨텍스트가 멱등 수신자로 구현되어야 함을 의미한다. 

즉, 같은 메시지를 여러 번 받아도 중복 처리가 일어나지 않도록 보장해야 합니다.

- 멱등: 연산을 여러 번 적용해도 결과가 달라지지 않는 성질 ex) abs(abs(x)) = abs(x)

 

적어도 한 번의 전달은 메시징 메커니즘이 특정 메시지를 주기적으로 재전달하는 메시징 패턴이다. 이것은 메시지 손실, 느린 반응, 수신자 장애, 수신자가 수신 사실을 알리는데 실패하는 상황에서도 적용된다. 

메시지가 한 번 이상 전달되면 수신자는 언제든지 이 상황을 제대로 처리할 수 있도록 설계해야 하며, 이는 오퍼레이션이 여러번 수행되더라도 동일한 결과가 되도록(멱등 수신자가 되도록) 만들어야 한다. 

즉, 수신자가 중복 제거, 반복 메시지 무시 등을 통해 오퍼레이션 재수행을 안전하게 처리한다는 것을 의미한다.


컨텍스트 매핑 사례

서로 다른 3개의 바운디드 컨텍스트 안에서 각 1 개씩 총 3개의 정책이 있다. 만약 "기록 정책"이 계약 심사 부서에 속한다고 가정해보면 다른 바운디드 컨텍스트들은 그것을 어떻게 참고해야 할까?

계약 심사 컨텍스트에서 정책 컴포넌트가 만들어지면 정책 발행이라는 이름의 도메인 이벤트를 발생시킬 수 있다. 메시징 구독을 통해 이를 제공받은 다른 바운디드 컨텍스트들이 이 도메인 이벤트에 반응할 때, 구독 바운디드 컨텍스트 안에 이 정책에 상응하는 정책 컴포넌트를 만들 수도 있다.

정책 발행 도메인 이벤트는 공식적으로 정책의 식별성을 담고 있으며, 정책 ID를 통해 정책들을 식별한다. 구독 바운디드 컨텍스트에 생성된 모든 컴포넌트는 발신 주체인 계약 심사 컨텍스트로의 역추적을 위해 그 식별자를 보유한다. 정책 발행 도메인 이벤트가 제공하는 것 이상의 정책 데이터가 필요하면, 구독 바운디드 컨텍스트는 계약 심사 컨텍스트로부터 더 많은 정보를 가져올 수 있다. 여기서 구독 바운디드 컨텍스트는 계약 심사 컨텍스트에서 쿼리를 수행하기 위해 정책 발행 ID를 사용한다.

계약 심사 컨텍스트에서 재쿼리를 어떻게 수행할까? 여러분은 계약 심사 컨텍스트 에 RESTful 공개 호스트 서비스와 공표된 언어를 설계할 수 있다. 즉, 간단한 HTTP GET API와 정책 발행 ID로 정책 발행 데이터를 검색할 수 있다.