도메인 주도 개발 스터디

Chapter 9 도메인 모델과 바운디드 컨텍스트

막이86 2023. 11. 8. 17:44
728x90

도메인 주도 개발 시작하기 9장을 요약한 내용입니다.

9.1 도메인 모델과 경계

  • 도메인 모델을 만들 때 빠지기 쉬운 함정이 도메인을 완벽하게 표현하는 단일 모델을 만드는 시도를 하는 것
  • 한 도메인은 여러 하위 도메인으로 구분되기 때문에 한 개의 모델로 여러 하위 도메인을 모두 표현하려고 시도하면 오히려 모든 하위 도메인에 맞지 않는 모델을 만들게 된다.
  • 상품이라는 모델을 생각 해보자.
    • 카달로그에서 상품, 재고 관리에서 상품, 주문에서 상품, 배송에서 상품은 이름만 같지 실제로 의미하는 것은 다름
    • 카달로그에서의 상품은 상품 이미지, 상품명, 상품 가격 등과 같은 상품 위주의 정보
    • 재고 관리에서의 상품은 실존하는 개별 객체를 추적관리하기 위한 목적
  • 논리적으로는 같은 존재처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우도 있다.
  • 한 갠의 모델로 모든 하위 도메인을 표현하려는 시도는 올바른 방법이 아니다.
  • 올바른 도메인 모델을 개발하려면 하위 도메인마다 모델을 만들어야 한다.
    • 모델은 명시적으로 구분되는 경계를 가져서 섞이지 않도록 해야한다.
    • 여러 하위 도메인의 모델이 섞이기 시작하면 모델의 의미가 약해질 뿐만 아니라 여러 도메인의 모델이 서로 얽히기 때문에 각 하위 도메인별로 다르게 발전하는 요구사항을 모델에 반영하기 어렵다.
  • 모델은 특정한 컨텍스트 하에서 완전한 의미를 갖는다.
    • 구분 되는 경계를 갖는 컨텍스트를 DDD에서는 바운디드 컨텍스트(Bounded Context) 라고 부른다.

9.2 바운디드 컨텍스트

  • 바운디드 컨텍스트는 모델의 경계를 결정하며 한 개의 바운디드 컨텍스트는 논리적으로 한개의 모델을 갖는다
  • 바운디드 컨텍스트는 용어를 기준으로 구분
  • 하위 도메인과 바운디드 컨텍스트가 일대일 관계를 가지면 좋겠지만 현실은 그렇지 않을 때가 많다.
  • 바운디드 컨텍스트는 기업의 팀 조직 구조에 따라 결정되기도 한다.
    • 주문 하위 도메인이라도 주문을 처리하는 팀과 복잡한 결제 금액 계산 로직을 구현하는 팀이 따로 있는 경우
      • 주문 하위 도메인에 주문 바운디드 컨텍스트와 결제 금액 계산 바운디드 컨텍스트가 존재
    • 용어를 명확하게 구문하지 못해 두 하위 도메인을 하나의 바운디드 컨텍스트에서 구현
      • 카탈로그와 재고 관리가 명확하게 구분되지 않은 경우
  • 전체 시스템을 한 개 팀에서 구현
    • 여러 하위 도메인을 한 개의 바운디드 컨텍스트에서 구현
  • 여러 하위 도메인을 하나의 바운디드 컨텍스트에서 개발할 때 주의할 점은 하위 도메인의 모델이 섞이지 않도록 하는 것
    • 전체 하위 도메인을 위한 단일 모델을 만들고 싶은 유횩에 빠지기 쉽다.
      • 개별 하위 도메인을 재대로 반영하지 못해서 하위 도메인별로 기능을 확정하기 어렵게 됨
    • 여러 하위 도메인을 포함하더라도 하위 도메인마다 구분되는 패키지를 갖도록 구현 해야함
  • 바운디드 컨텍스트는 도메인 모델을 구분하는 경계가 되기 때문에 바운디드 컨텍스트는 구현하는 하위 도메인에 알맞은 모델을 포함
    • 주문 바운디드 컨텍스트와 회원 바운디드 컨텍스트가 갖는 사용자 모델이 달라진다.
    • 같은 상품이라도 카달로그 바운디드 컨텍스트의 Product와 재고 바운디드 컨텍스트의 Product는 각 컨텍스트에 맞는 모델을 갖는다.
  • 회원의 Member 애그리거트 루트이지만 주문의 Orderer는 밸류가 됨
  • 카달로그의 Product는 상품에 속할 Category와 연관을 갖지만 재고의 Product는 카달로그의 Category와 연관을 맺지 않는다.

9.3 바운디드 컨텍스트 구현

  • 바운디드 컨텍스트가 도메인 모델에만 포함하는 것은 아니다.
    • 도메인 기능을 사용자에게 제공하는데 필요한 표현 영역, 응용 서비스, 인프라스트럭처 영역을 모두 포함 한다.
  • 도메인 모델의 데이터 구조가 바뀌면 DB 테이블 스키마도 함께 변경해야 하므로 테이블도 바운디드 컨텍스트에 포함된다.
  • 모든 바운디드 컨텍스트를 반드시 도메인 주도 개발할 필요는 없다.
    • 상품의 리뷰는 복잡한 도메인 로직을 갖지 않기 때문에 CRUD 방식으로 구현해도 된다.
    • 단순하면 서비스 - DAO로 구성된 CRUD 방식을 사용해도 유지보수하는데 문제가 되지 않는다.
  • 한 바운디드 컨텍스트에서 두 방식을 혼합해서 사용될 수 있다.
    • CQRS(Command Query Responsibility Segregation) 패턴을 단일 바운디드 컨텍스트에 적용하면 상태 변경과 관련된 기능을 도메인 모델 기반으로 구현하고 조회 기능은 서비스-DAO를 이용해서 구현할 수 있다.
    <aside> 👉 CQRS 패턴은 상태를 변경하는 명령 기능과 내용을 조회하는 쿼리 기능을 위한 모델을 구분하는 패턴
  • </aside>
  • 각 바운디드 컨텍스트는 서로 다른 구현 기술을 사용할 수도 있다.
    • 웹 MVC는 스프링 MVC를 사용하고 리포지터리 구현 기술로는 JPA/하이버네티를 사용는 바운디드 컨텍스트
    • Netty를 이용해서 REST API를 제공하고 마이바티스를 리포지터리 구현 기술로 사용하는 바운디드 컨텍스트
  • 바운디드 컨텍스트가 반드시 사용자에게 보여지는 UI를 가지고 있어야 하는 것은 아니다.
    • REST API를 직접 호출해서 로딩한 JSON 데이터를 보여줄 수 있다.
    • UI 처리하는 서버를 두고 UI 서버에서 바운디드 컨테스트와 통신해서 사용자 요청을 처리

9.4 바운디드 컨텍스트 간 통합

  • 온라인 쇼핑 사이트에서 매출 증대를 위해 카달로그 하위 도메인에 개인화 추천 기능을 도입
    • 기존 시스템을 개발하던 팀과 별도로 추천 시스템을 담당하는 팀이 생김
    • 카달로그 하위 도메인에는 기존 카달로그를 위한 바운디드 컨텍스트와 추천 기능을 위한 바운디드 컨텍스트가 생긴다
  • 두 팀이 관련된 바운디드 컨텍스트를 개발하면 자연스럽게 두 바운디드 컨텍스트 간 통합이 발생
    • 사용자가 제품 상세 페이지를 볼 떄, 보고 있는 상품과 유사한 상품 목록을 하단에 보여준다.
  • 카달로그 컨텍스트와 추천 컨텍스트의 도메인 모델은 서로 다르다.
    • 카달로그는 제품을 중심으로 도메인 모델을 구현
    • 추천은 추천 연산을 위한 모델을 구현
  • 카달로그 시스템은 추천 시스템으로 추천 데이터를 받아오지만, 카달로그 시스템에서는 카달로그 도메인 모델을 사용해서 추천 상품을 표현해야 한다.
  • // 상품 추천 기능을 표현하는 도메인 서비스 public interface ProductRecommendationService { List<Product> getRecommendationsOf(ProductId id); }
  • 도메인 서비스를 구현한 클래스는 인프라스트럭처 영역에 위치
    • 외부 시스템과의 연동을 처리하고 외부 시스템의 모델과 현재 도메인 모델 간의 변환을 책임
  • 외부 추천 시스템이 제공하는 REST API를 이용해서 특정 상품을 위한 추천 상품 목록을 로딩
    • 추천 시스템의 모델을 기반으로 하기 있기 때문에 API 응답은 카달로그 도메인 모델과 일치하지 않는 데이터를 제공
    [
    	{itemId: 'PROD-1000', type: 'PRODUCT', rank: 100},
    	{itemId: 'PROD-1001', type: 'PRODUCT', rank: 54},
    ]
    
  • REST API로 데이터를 읽어와 카달로그 도메인에 맞는 상품 모델로 변환
    • 외부 추천 시스템의 모델을 받아와 toProducts()를 이용해서 카달로그의 Product 모델로 변환
    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) {
    		return externalRecClient.getRecs(itemId);
    	}
    
    	private List<Product> toProducts(List<RecommendationItem> items) {
    		return items.stream()
    						.map(item -> toProductId(item.getItemId())
    						.map(prodId -> productRepository.findById(prodId))
    						.collect(toList());
    	}
    
    	private ProductId toProductId(String itemId) {
    		return new ProductId(itemId);
    	}
    }
    
  • 두 모델 간의 변환 과정이 복잡하면 변환 처리를 위한 별도 클래스를 만들고 변환을 처리해도 된다.
  • REST API를 호출하는 것은 두 바운디드 컨텍스트를 직접 통합하는 방법이다.
  • 직접 통합하는 대신 간접적으로 통합하는 방법도 있다.
    • 메시지 큐를 사용하여 필요로 하는 내역을 전달
  • 추천 바운디드 컨텍스트는 큐에서 이력 메시지를 읽어와 추천을 계산하는데 사용
    • 두 바운디드 컨텍스트가 사용할 메시지의 데이터 구조를 맞춰야 함을 의미
    • 메시지 시스템은 카탈로그 측에서 관리, 큐에 담기는 메시지는 카탈로그 도메인을 따르는 데이터를 담는다
    • 추천 바운디드 컨텍스트 관점에서 접근하면 데이터 구조는 변경될 수 있다.
  • 어떤 도메인 관점에서 모델을 사용하느냐에 따라 두 바운디드 컨텍스트의 구현 코드가 달라지게 된다.
  • // 상품 조회 관련 로그 기록 public class ViewLogService { private MessageClient messageClient; public void appendViewLog(String memberId, String productId, Date time) { messageClient.send(new ViewLog(memberId, productId, time)); } } // messageClient public class RabbitMQClient implements MessageClient { private RabbitTemplate rabbitTemplate; @Override public void send(ViewLog viewLog) { // 카탈로그 기준으로 작성한 데이터를 큐에 그대로 보관 rabbitTemplate.convertAndSend(logQueueName, viewLog); } }
  • 추천 시스템을 기준으로 큐에 데이터를 저장하기로 했다면 코드는 변경된다.
  • // 상품 조회 관련 로그 기록 public class ViewLogService { private MessageClient messageClient; public void appendViewLog(String memberId, String productId, Date time) { messageClient.send(new ActivityLog(productId, memberId, ActivityType.VIEW, time)); } } // messageClient public class RabbitMQClient implements MessageClient { private RabbitTemplate rabbitTemplate; @Override public void send(ActivityLog activityLog) { rabbitTemplate.convertAndSend(logQueueName, activityLog); } }
  • 두 바운디드 컨텍스트를 개발하는 팀은 메시징 큐에 담을 데이터의 구조를 협의하게 되는데 그 큐를 누가 제공하느냐에 따라 데이터 구조가 결정됨

9.5 바운디드 컨텍스트 간 관계

  • 바운디드 컨텍스트는 어떤 식으로든 연결되기 때문에 두 바운디드 컨텍스트는 다양한 방식으로 관계를 맺는다.
    • 가장 흔한 관계는 한쪽에서 API를 제공하고 한쪽에서는 API를 호출하는 관계이다.
    • API를 사용하는 바운디드 컨텍스트는 API를 제공하는 바운디드 컨텍스트에 의존하게 된다.
  • 하류(downstream) 컴포넌트인 카탈로그 컨텍스트는 상류(upstream) 컴포넌트인 추천 컨텍스트가 제공하는 데이터와 기능에 의존한다.
  • 상류 컴포넌트는 일종의 서비스 공급자 역활을 하며 하류 컴포넌트는 그 서비스를 사용하는 고객 역활을 한다.
    • 상류 컴포넌트는 보통 하류 컴포넌트가 사용할 수 있는 통신 프로토콜을 정의하고 이를 공개
    • 상류 팀의 고객인 하류 팀이 다수 존재하면 상류 팀으 여러 하루팀의 요구사항을 수용할수 있는 API를 제공
    • 이런 서비스를 공개 호스트 서비스(OPEN HOST SERVICE)라고 한다.
  • 상류 컴포넌트의 서비스는 상류 바운디드 컨텍스트의 도메일 모델을 따른다.
    • 상류 서비스의 모델은 자신의 도메인 모델에 영향을 주지 않도록 보호해 주는 완충 지대를 만들어야 한다.
  • 외부 시스템 도메인 모델이 내 도메인 모델을 침범하지 않도록 막아주는 역활이 필요
    • 내 모델이 깨지는 것을 막아주는 안티코럽션 계층
    • RecSystemClient가 안티코럽션 계층
  • 두 바운디드 컨텍스트가 같은 모델을 공유하는 경우도 있다.
    • 운영자를 위한 주문 관리 도구, 고객을 위한 주문 서비스
    • 주문을 표현하는 모델을 공유하는 모델을 공유 커널(SHARED KERNEL)이라 부른다.
    • 공유 커널의 장점은 중복을 줄여준다는 것
  • 독립 방식(SEPARATE WAY) 관계
    • 두 바운디드 컨텍스트 간에 통합하지 않으므로 서로 독립적으로 모델을 발전
    • 독립 방식에서 두 바운디드 컨텍스트 간의 통합은 수동으로 이루어진다.
    • 쇼핑몰 솔루션과 ERP 서비스를 사용할 경우
      • 쇼핑몰 솔루션은 ERP 서비스와 연동을 지원하지 않음
      • 쇼핑몰에서 판매가 발생하면 쇼핑몰 운영자는 판매 정보를 보고 ERP 시스템에 입력 해야함
  • 수동으로 통합하는 방식이 나쁜 것은 아니지만 규모가 커질수록 수동 통합에 한계가 있다
    • 두 바운디드 컨텍스트를 통합해주는 별도의 시스템을 만들어 해결 가능

9.6 컨텍스트 맵

  • 개별 바운디드 컨텍스트에 매몰되면 전체를 보지 못할 때가 있다.
    • 전체 비즈니스를 조망할 수 잇는 지도가 필요한테 그것이 바로 컨텍스트 맵
  • 바운디드 컨텍스트 영역에 주요 애그리거트를 함께 표시하면 모델에 대한 관계가 더 명확하게 드러난다.
    • 오픈 호스트 서비스(OHS)와 안티코럽션 계층(ACL)만 표시
    • 하위 도메인이나 조직 구조를 함께 표시하면 도메인을 포함한 전체 관계를 이해하는데 도움 됨
  • 컨텍스트 맵은 시스템의 전체 구조를 보여준다.
  • 컨텍스트 맵을 그리는 규칙은 따로 없다.
    • 간단한 도영화 선을 이용해서 각 컨텍스트의 관계를 이해할 수 있는 수준에서 그리면 된다.
728x90

'도메인 주도 개발 스터디' 카테고리의 다른 글

Chapter 11 CQRS  (0) 2023.11.09
Chapter 10 이벤트  (0) 2023.11.09
Chapter 8 애그리거트 트랜잭션 관리  (0) 2023.11.08
Chapter 7 도메인 서비스  (0) 2023.11.08
Chapter 6 응용 서비스와 표현 영역  (0) 2023.11.08