효습
9장 도메인 모델과 바운디드 컨텍스트 본문
9.1 도메인 모델과 경계
- 처음 도메인 모델을 만들 때 빠지기 쉬운 함정이 도메인을 완벽하게 표현하는 단일 모델을 만드는 시도를 하는 것이다.
- 논리적으로 같은 존재처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우가 있다.
- 한 개의 모델로 모든 하위 도메인을 표현하려는 시도는 올바른 방법이 아니며 표현할 수도 없다.
→ 하위 도메인마다 모델을 만들어야 한다.
- 각 모델은 명시적으로 구분되는 경계를 가져서 섞이지 않도록해야 한다.
- 여러 하위 도메인의 모델이 섞이기 시작하면 모델의 의미가 약해질 수 있다.
- 여러 도메인의 모델이 서로 얽히기 때문에 각 하위 도메인별로 다르게 발전하는 요구사항을 모델에 반영하기 어려워진다.
- 모델은 특정한 컨텍스트(문맥) 하에서 완전한 의미를 갖는다.
- 이렇게 구분되는 경계를 갖는 컨텍스트를 DDD에서는 바운디드 컨텍스트(Bounded Context)라고 한다.
9.2 바운디드 컨텍스트
바운디드 컨텍스트는 모델의 경계를 결정하며 한 개의 바운디드 컨텍스트는 논리적으로 한 개의 모델을 갖는다.
- 바운디드 컨텍스트는 용어를 기준으로 구분한다.
- 바운디드 컨텍스트는 실제로 사용자에게 기능을 제공하는 물리적 시스템으로 도메인 모델은 바운디드 컨텍스트 안에서 도메인을 구현한다.
대부분의 경우 하위 도메인과 바운디드 컨텍스트는 일대일 관계를 가지지 않는다.
여러 하위 도메인을 하나의 바운디드 컨텍스트에서 개발할 때 주의할 점은 하위 도메인의 모델이 섞이지 않도록 하는 것이다.
만약 전체 하위 도메인을 위한 단일 모델을 만든다면
- 결과적으로 도메인 모델이 개별 하위 도메인을 제대로 반영하지 못해서 하위 도메인별로 기능을 확장하기 어렵게 되고
- 서비스의 경쟁력을 떨어뜨리는 원인이 된다.
한 개의 바운디드 컨텍스트가 여러 하위 도메인을 포함하더라도 하위 도메인마다 구분되는 패키지를 갖도록 구현해야하며, 이렇게 함으로써 하위 도메인을 위한 모델이 서로 뒤섞이지 않고 하위 도메인마다 바운디드 컨텍스트를 갖는 효과를 낼 수있다
바운디드 컨텍스트는 도메인 모델을 구분하는 경계가 되기 때문에 바운디드 컨텍스트는 구현하는 하위 도메인에 알맞는 모델을 포함해야한다.
9.3 바운디드 컨텍스트 구현
바운디드 컨텍스트가 도메인 모델만 포함하는 것이 아니라 도메인 기능을 사용자에게 제공하는데 필요한 모든 영역(표현 영역 , 응용 서비스 , 인프라스트럭처, DBMS)을 포함한다.
- 그렇다고 모든 바운디드 컨텍스트를 반드시 도메인 주도로 개발할 필요는 없다.
- 상품의 리뷰와 같이 복잡한 도메인 로직을 갖지 않는다면 CRUD 방식으로 구현해도 된다.
- 즉 DAO와 데이터 중심의 밸류 객체를 이용해서 리뷰 기능을 구현해도 기능을 유지 보수하는데 큰 문제가 없다.
- 서비스-DAO 구조를 사용하면 도메인 기능이 서비스에 흩어지게 되지만 도메인 기능 자체가 단순하면 이와 같은 CRUD 방식을 사용해도 코드를 유지 보수하는데 문제가 되지 않는다고 필자는 생각한다.
- 한 바운디드 컨텍스트에서 두 방식을 혼합해서 사용할 수 있다.
- 대표적인 예가 CQRS(상태를 변경하는 명령 기능과 내용을 조회하는 쿼리 기능을 위한 모델을 구분하는) 패턴이다.
- 이 패턴을 단일 바운디드 컨텍스트에 적용하면 아래와 같이 상태 변경과 관련된 기능은 도메인 모델 기반으로 구현하고 조회 기능은 서비스-DAO를 이용해서 구현할 수 있다.
- 대표적인 예가 CQRS(상태를 변경하는 명령 기능과 내용을 조회하는 쿼리 기능을 위한 모델을 구분하는) 패턴이다.
- 각 바운디드 컨텍스트는 서로 다른 구현 기술을 사용할 수도 있다.
- 웹 MVC는 스프링 MVC를 사용하고 리포지터리 구현 기술로는 JPA/하이버네이트를 사용하는 바운디드 컨텍스트가 존재할 수 있다.
- 바운디드 컨텍스트가 반드시 사용자에게 보여지는 UI를 가지고 있어야 하는 것은 아니다
- 원하는 바운디드 컨텍스트의 REST API를 직접 호출해서 로딩한 JSON 데이터를 알맞게 가공하여 사용할 수 있다.
- UI를 처리하는 서버를 두고 UI 서버에서 바운디드 컨텍스트와 통신해서 사용자 요청을 처리하는 방법도 있다. ( 이 경우 UI서버는 각 바운디드 컨텍스트의 파사드 역할을 수행할 수 있다.)
9.4 바운디드 컨텍스트 간 통합
온라인 쇼핑 사이트에서 매출 증대를 위한 카탈로그 하위 도메인에 개인화 추천 기능을 도입하기로 했다고 하자
- 기존 카탈로그 시스템을 개발하던 팀과 별도로 추천 시스템을 개발하는 팀이 생겨 각각의 바운디드 컨텍스트가 생긴다.
- 두 바운디드 컨텍스트 간 통합 해야하는 상황이 생기는데 통합이 필요한 기능은 다음과 같다.
- 사용자가 제품 상세 페이지를 볼 때, 보고 있는 상품과 유사한 상품 목록을 하단에 보여준다.
- 사용자가 카탈로그 바운디드 컨텍스트에 추천 제품 목록 요청 → 카탈로그 바운디드 컨텍스트가 추천 바운디드 컨텍스트로부터 추천 정보를 읽어와 추천 제품 목록 제공
- 카탈로그 컨텍스트와 추천 컨텍스트의 도메인 모델은 서로 다른다.
- 카탈로그는 제품을 중심으로 도메인 모델을 구현하지만 , 추천은 추천 연산을 위한 모델을 구현한다.
- 카탈로그 시스템은 추천 시스템으로부터 추천 데이터를 가져와 카탈로그 모델을 기반으로 상품 추천 기능을 표현해야한다.
- 도메인 서비스를 구현한 클래스는 인프라스트럭처 영역에 위치한다.
- 이 클래스는 외부 시스템과의 연동을 처리하고 외부 시스템의 모델과 현재 도메인 모델 간의 변환을 책임진다.(여기서는
RecSystemClient
) - 인프라에 속하는 클래스가 REST API 호출로 응답을 받으면 이는 카탈로그 도메인 모델과 일치하지 않을 수 있다.
- 그래서 이 클래스(
RecSystemClient
)는 이 응답을 카탈로그 도메인에 맞는 상품 모델로 변환한다. - 두 모델 간의 변환 과정이 복잡하면 별도의 클래스를 만들어 그 클래스에서 변환을 처리해도 된다.
- 이 클래스는 외부 시스템과의 연동을 처리하고 외부 시스템의 모델과 현재 도메인 모델 간의 변환을 책임진다.(여기서는
//카탈로그 도메인의 product 모델로 변환하는 작업
private List<Product> toProducts(List<RecommendationItem> items){
return items.stream()
.map(item -> toProductId(item.getItemId()) )
.map(prodId > productRepository.findById(prodId) )
.collect(toList());
}
- 앞서 언급한 방식은 직접 통합하는 방식이라고 볼 수 있고 메세지 큐로 통합하는 간접 통합 방식이 있다.
- 메세지 큐는 비동기로 메세지를 처리하기 때문에 카탈로그 바운디드 컨텍스트는 메세지를 큐에 추가한 뒤에 추천 바운디드 컨텍스트가 메세지를 처리할 때까지 기다리지 않고 바로 이어서 자신의 처리를 계속할 수 있다.
- 그렇다면 이 두 바운디드 컨텍스트가 메세지의 데이터 구조를 맞춰야하는데 이는 어느 쪽에서 메세지 시스템을 담당하고 있냐에 따라 달라진다.
- 큐를 누가 제공하느냐에 따라서도 데이터 구조가 달라진다.
- 카탈로그 시스템에서 큐를 제공한다면 담기는 내용은 카탈로그 도메인에 따른다.
- 다른 바운디드 컨텍스트는 이 큐로부터 필요한 메세지를 수신하는 방식을 사용한다.
- 출판/구독 모델을 따르는 것이다.
- 큐를 추천 시스템에서 제공할 경우, 큐로 인해 비동기로 데이터를 전달하는 것을 제외하면 REST API를 사용해서 데이터를 전달하는 것과 차이가 없다.
마이크로서비스와 바운디드 컨텍스트
마이크로 서비스는 애플리케이션을 작은 서비스로 나누어 개발하는 아키텍처 스타일이다.
- 개별 서비스를 독립된 프로세스로 실행하고 각 서비스가 REST API나 메시징을 이용해서 통신하는 구조를 가진다.
- 이런 마이크로서비스의 특징은 바운디드 컨텍스트와 잘 어울린다.
- 마이크로 서비스로 바운디드 컨텍스트를 구현하면 자연스텁게 컨텍스트별로 모델이 분리된다.
- 별도의 프로세스로 개발한 바운디드 컨텍스트는 독립적으로 배보하고 모니터링하며 확장되는데 이는 마이크로서비스가 갖는 특징이다.
9.5 바운디드 컨텍스트 간 관계
- 상류 컴포넌트는 일종의 서비스 공급자 역할을 하면 하류 컴포넌트는 그서비스를 사용하는 고객 역할을 한다.
- 상류 컴포넌트는 보통 하류 컴포넌트가 사용할 수 있는 통신 프로토콜을 정의하고 이를 공개한다.
- REST API 혹은 프로토콜 버퍼
- 상류 팀의 고객인 하류 팀이 다수 존재할 경우 여러 하류팀의 요구사항을 수용할 수 있는 API를 만들고 이를 서비스 형태로 공개해서 서비스의 일관성을 유지할 수 있다. 이런 서비스를 공개 호스트 서비스라고 한다.(OPEN HOST SERVICE).
- 검색이 대표적인 예이다. 검색을 위한 전용 서비스를 구축하고 검색 시스템과 블로그, 카페와 같은 각 서비스를 통합한다.
- 상류 팀은 각 하류 컴포넌트의 요구사항을 수용하는 단일 API를 만들어 이를 공개하고 각 하류팀은 이를 사용하여 기능을 구현한다.
- 상류 컴포넌트의 서비스는 상류 바운디드 컨텍스트의 도메인 모델을 따른다.
- 하류 컴포넌트는 상류 서비스의 모델이 자신의 도메인 모델에 영향을 주지 않도록 보호해 주는 완충 지대를 만들어야한다.
- 앞서 언급한 infra에 속하는
RecSystemClient
와 같이 외부 시스템과의 연동을 처리하는데 외부 시스템의 도메인이 내 도메인 모델을 침범하지 않도록 막아주는 역할을 하는 역할을 하는 클래스가 필요하다.
- 두 바운디드 컨텍스트가 같은 모델을 공유하는 경우도 있다.
- 모델을 공유함으로써 주문과 관련된 중복 설계를 막을 수 있다.
- 두 바운디드 컨텍스트가 공유하는 모델을 공유 커널(Shared Kernel)이라고 한다.
- 공유 커널의 장점은 중복을 줄여준다. → 두 컨텍스트가 밀접한 관계를 형성하지 못한다면 장점이 단점이 될 수 있음
- 두 바운디드 컨텍스트가 통합하지 않는 독립 방식(Separate Way)도 있다.
- 독립 방식에서 두 바운디드 컨텍스트 간 통합은 수동으로 이루어진다.
- 수동으로 통합하는 방식이 나쁜 것은 아니지만 규모가 커질수록 수동 통합에는 한계가 있으므로 규모가 커지기 시작하면 두 바운디드 컨텍스트를 통합해야 한다.
9.6 컨텍스트 맵
컨텍스트는 시스템의 전체 구조를 보여준다.
- 하위 도메인과 일치하지 않는 바운디드 컨텍스트를 찾아 도메인에 맞게 바운디드 컨텍스트를 조절하고
- 사업의 핵심 도메인을 위해 조직 역량을 어떤 바운디드 컨텍스트에 집중할지 파악하는 데 도움을 준다.
'책 > 도메인 주도 개발 시작하기' 카테고리의 다른 글
11장 CQRS (0) | 2024.05.07 |
---|---|
10장 이벤트 (0) | 2024.05.07 |
8장 애그리거트 트랜잭션 관리 (1) | 2024.04.01 |
7장 도메인 서비스 (0) | 2024.04.01 |
6장 응용 서비스와 표현 영역 (0) | 2024.03.25 |