도메인 주도 개발 시작하기 책 정리
도메인 모델과 경계
✅ 모델은 도메인마다 의미가 다를 수 있다
도메인을 완벽하게 표현하는 단일 모델은 구현하기 어렵다.
ex) 상품 모델
카탈로그 도메인, 재고 관리 도메인, 주문 도메인에서 상품 모델이 의미하는 것은 다르다.
-> 도메인마다 다른 용어를 사용하는 경우도 있다. (카탈로그 도메인 - 상품, 검색 도메인 - 문서)
이처럼 한 개의 모델로 모든 하위 도메인을 표현할 수 없다.
-> 하위 도메인마다 모델을 만들어야 한다.
-> 그리고 각 모델은 명시적으로 구분되는 경계를 가져서 섞이지 않도록 한다.
-> 이러한 경계를 DDD에서는 바운디드 컨텍스트(Bounded Context)라고 부른다.
바운디드 컨텍스트
✅ 모델의 경계를 결정
실제로 사용자에게 기능을 제공하는 물리적 시스템 (회원, 주문, 재고, ...)
✅ 다양한 구조의 바운디드 컨텍스트
이상적인 모습: 하위 도메인과 바운디드 컨텍스트가 일대일 관계
-> 그러나 현실은 그렇지 않을 때가 많다.
바운디드 컨텍스트는 기업의 팀 조직 구조에 따라 결정되기도 한다.
-> 한 도메인에 기여하는 두 개의 팀이 있다면, 하나의 도메인에 두 개의 바운디드 컨텍스트가 존재하게 된다.
-> 하나의 바운디드 컨텍스트에서 두 개의 도메인을 구현하기도 한다.
✅ 하나의 바운디드 컨텍스트만 존재
규모가 작은 기업은 전체 시스템을 한 개 팀에서 구현할 때도 있다.
-> 여러 도메인을 한 개의 바운디드 컨텍스트에서 구현한다.
이 때, 주의할 점은 모델이 섞이지 않도록 하는 것이다.
하나의 모델도 각 도메인마다 의미가 다를 수 있기 때문에,
하나의 바운디드 컨텍스트라도 각 도메인마다 구분되는 패키지를 만들고, 각 도메인을 위한 모델을 만들어야 한다.
-> 물리적으로 한 바운디드 컨텍스트지만, 논리적으로 하위 바운디드 컨텍스트를 만드는 효과]
바운디드 컨텍스트 구현
✅ 바운디드 컨텍스트는 도메인 모델만 포함하는게 아니다
바운디드 컨텍스트는 도메인 기능을 사용자에게 제공하는데 필요한 표현 영역, 응용 서비스, 인프라스트럭처, DB테이블까지 모두 포함한다.
✅ 바운디드 컨텍스트 별로 구현을 다르게
각각의 바운디드 컨텍스트는 구현 방식을 다르게 해도 된다.
ex) 어떤 컨텍스트는 DDD로, 어떤 컨텍스트는 DAO와 데이터 중심으로, 어떤 컨텍스트는 CQRS를 통해 DDD, DAO 모두 이용해도 된다.
또한, 다른 구현 기술을 사용해도 된다.
ex) 어떤 컨텍스트는 MVC + JPA로, 어떤 컨텍스트는 Netty + Mybatis로.
또한, 어떤 컨텍스트는 HTML 응답을, 어떤 컨텍스트는 JSON 응답을 할 수도 있다.
-> 혹은 브라우저와 두 컨텍스트 가운데 UI 서버를 둬서 브라우저는 UI 서버에 요청하고, UI 서버가 각 컨텍스트의 정보(HTML, JSON)을 조합해서 브라우저에 내려주는 방식도 있다.
-> 여기서 말하고자 하는 핵심은 각 바운디드 컨텍스트의 특성에 맞게 구현 방법, 구현 기술을 선택하면 된다는 것이다.
바운디드 컨텍스트 간 통합
✅ 한 도메인에서 두 팀이 협업
ex) 온라인 쇼핑 사이트에서 매출 증대를 위해 카탈로그 하위 도메인에 개인화 추천 기능을 도입하기로 했다.
요구사항: 사용자가 제품 상세 페이지를 볼 때, 보고 있는 상품과 유사한 상품 목록을 보여준다.
기존 카탈로그 시스템을 개발하던 팀과, 별도로 추천 시스템을 담당하는 팀이 있다.
-> 이렇게 되면 카탈로그 하위 도메인에는 기존 카탈로그 바운디드 컨텍스트에 새로운 추천 바운디드 컨텍스트가 추가된다.
✅ 카탈로그 시스템과 추천 시스템에서 상품 모델은 다르다
앞서 계속 강조했듯이 각 바운디드 컨텍스트마다 모델의 의미는 다르다.
-> 위 예제를 통해 각 컨텍스트마다 다른 모델에 대해 어떻게 서로 통신하는지 알아보자.
먼저 카탈로그에서 추천 상품을 보여주기 위해,
추천의 상품 모델을 사용하는 것이 아닌,
카탈로그 모델을 기반으로 하는 도메인 서비스를 이용해서 상품 추천 기능을 표현해야 한다.
public interface ProductRecommendationService {
List<Product> getRecommendationsOf(ProductId id);
}
도메인 서비스를 구현한 클래스는 인프라스트럭처 영역에 있다.
-> 외부 시스템과의 연동을 처리하고 외부 시스템의 모델과 도메인 모델 간의 변환을 책임진다.
✅ 외부 시스템의 데이터를 도메인에 맞게 변환
RecSystemClient는 외부 추천 시스템이 제공하는 REST API를 이용해서 추천 상품 목록을 조회한다.
단, REST API가 제공하는 데이터는 다음과 같이, 추천 시스템의 모델을 기반으로 하고 있을 것이다.
[
{itemId: 'PROD-1000', type: 'PRODUCT', rank: 100},
{itemId: 'PROD-1001', type: 'PRODUCT', rank: 54}
]
RecSystemClient는 REST API로부터 데이터를 읽어와 카탈로그 도메인에 맞는 상품 모델로 변환한다.
public class RecSystemClient implements ProductRecommendationService {
private ProductRepository productRepository;
@Override
public List<Product> getRecommendationsOf(ProductId id) {
List<RecommendationItem> items = getRecItems(id.getValue);
return toProducts(items);
}
private List<RecommendationItem> getRecItems(String itemId) {
// externalClient는 외부 추천 시스템을 위한 클라이언트라고 가정
return externalRecClient.getRecs(itemId);
}
private List<Product> toProducts(List<RecommendationItem> items) {
return items.stream()
.map(item -> toProductId(item.getItemId()))
.map(proId -> productRepository.findById(proId))
.collect(toList());
}
private ProductId toProductId(String itemId) {
return new ProductId(itemId);
}
}
+) 두 모델 간의 변환 과정이 복잡하면 변환 처리를 위한 별도 클래스를 만들어도 된다.
✅ 바운디드 컨텍스트 간접적 통합: 메시지 큐
REST API를 호출하는 것은 두 바운디드 컨텍스트를 직접 통합하는 방법이다.
직접 통합하는 대신 간접 통합하는 방법이 있다.
-> 메시지 큐 사용.
ex) 추천 시스템은 사용자가 조회한 상품 이력이나 구매 이력과 같은 사용자 활동 이력을 필요로 한다.
이 때, 카탈로그 시스템은 이력을 메시지 큐에 추가하고, 추천 시스템은 큐에서 메시지를 가져온다.
-> 비동기로 수행할 수 있다는 장점이 있다.
여기서 중요한건 카탈로그 시스템의 담당 팀과 추천 시스템 담당 팀이 협의해서 메시지의 데이터 구조를 맞춰야 한다는 점이다.
-> 메시지를 카탈로그 도메인 관점에서 관리하냐, 추천 도메인 관점에서 관리하느냐에 따라 메시지 구조도 달라진다.
ex) 카탈로그 도메인 관점에서 메시지를 생성 (카탈로그에서 ViewLog라는 형태로 이력을 관리할 때)
public class ViewLogService {
private MessageClient messageClient;
public void appendViewLog(String memberId, String productId, Date time) {
messageClient.send(new ViewLog(memberId, productId, time));
}
}
public class RabbitMQClient implements MessageClient {
private RabbitTemplate rabbitTemplate;
@Override
public void send(ViewLog viewLog) {
rabbitTemplate.convertAndSend(logQueueName, viewLog);
}
}
-> 만약 추천 도메인 관점에서 메시지를 생성한다면 해당 형식에 맞는 데이터(ViewLog가 아닌 다른 객체 형태)로 메시지를 전달하면 된다.
-> 일반적으로 큐에 메시지를 제공하는 쪽(카탈로그 시스템)에서 메시지 구조를 정한다.
+) 물리적으로 분리된 각 바운디드 컨텍스트마다 REST API 혹은 메시지 큐로 통신하는 구조는 마이크로서비스에 적합한 구조이기도 하다.
바운디드 컨텍스트 간 관계
✅ 고객/공급자 관계
두 바운디드 컨텍스트 간 관계 중 가장 흔한 관계로 대표적인 예로 REST API가 있다.
-> 이 관계에서 API를 사용하는 쪽은 API를 제공하는 쪽에 의존하게 된다.
하류(downstream) 컴포넌트인 카탈로그 컨텍스트는 상류(upstream) 컴포넌트인 추천 컨텍스트가 제공하는 데이터와 기능에 의존한다.
-> 상류가 바뀌게 되면 하류도 같이 바뀌게 된다.
-> 따라서 상류 팀과 하류 팀은 개발 계획을 서로 공유하고 일정을 협의해야 한다.
✅ 공개 호스트 서비스(Open Host Service)
상류 팀은 여러 하류 팀의 요구사항을 수용할 수 있는 API를 만들고 이를 서비스 형태로 공개하는 것.
ex) 블로그, 카페, 게시판 서비스는 상류 공개 호스트 서비스인 검색 서비스를 의존한다.
✅ 안티코럽션 계층(Anticorruption Layer)
상류 서비스는 상류 바운디드 컨텍스트의 도메인 모델을 따른다.
다만, 하류 컴포넌트는 상류 서비스의 모델이 자신의 모델에 영향을 주지 않도록 보호해 주는 완충 지대를 만들어야 한다.
-> 앞서 설명한 RecSystemClient가 이런 역할을 해준다. (외부 시스템 연동 + 두 시스템 간 모델 변환)
✅ 공유 커널(Shared Kernel)
두 바운디드 컨텍스트가 공유하는 모델.
하나의 모델을 두 바운디드 컨텍스트(팀)이 공유함으로써 중복 설계를 막을 수 있다.
-> 다만 두 팀이 한 모델을 공유하기 때문에 임의로 모델을 변경하는 것을 주의해야 하며 두 팀이 밀접한 관계를 유지해야 한다.
✅ 독립 방식(Separate Way)
반대로 모델을 서로 통합하지 않는 방식.
-> 각 바운디드 컨텍스트 간에 서로 독립적으로 모델을 발전시킨다.
독립 방식에서 두 바운디드 컨텍스트 간의 통합은 수동으로 이루어진다.
ex) 온라인 쇼핑몰(바운디드 컨텍스트)에서 판매가 발생하면 운영자가 직접 수동으로 ERP 시스템(바운디드 컨텍스트)에 입력해야 한다.
-> 시스템이 커지다보면 수동 방식에는 한계가 있으므로, 두 컨텍스트를 통합하기 위해 별도의 통합 시스템을 만드는 경우도 있다.
컨텍스트 맵
✅ 숲을 보자
그림은 오픈 호스트 서비스(OHS)와 안티코럽션 계층(ACL)만 표시했는데, 하위 도메인이나 조직 구조를 함께 표시하면 전체 관계를 이해하는데 도움이 된다.
'book > 도메인 주도 개발 시작하기' 카테고리의 다른 글
[DDD Start] 이벤트 (2) | 2023.09.08 |
---|---|
[DDD Start] 애그리거트 트랜잭션 관리 (0) | 2023.09.02 |
[DDD Start] 도메인 서비스 (0) | 2023.08.18 |
[DDD Start] 응용 서비스와 표현 영역 (0) | 2023.08.12 |
[DDD Start] 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2023.08.12 |