도메인 주도 개발 시작하기 - 9. 도메인 모델과 바운디드 컨텍스트

 Date: 2022-11-07

9.1 도메인 모델과 경계
9.2 바운디드 컨텍스트
9.3 바운디드 컨텍스트 구현
9.4 바운디드 컨텍스트 간 통합
9.5 바운디드 컨텍스트 간 관계
9.6 컨텍스트 맵

9.1 도메인 모델과 경계

  • 도메인을 완벽하게 표현하는 단일 모델을 만들려고 시도하면 오히려 하위 도메인에 맞지 않는 모델을 만들게 된다.
    • 카탈로그의 상품, 재고 관리의 상품, 주문의 상품, 배송의 상품은 모두 다른 의미를 갖는 상품이다.
  • 논리적으로 같은 것처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우도 있다.
    • 사용자 개념을 회원 도메인에서는 회원으로 부르고, 주문 도메인에서는 주문자로 부른다.
  • 하위 도메인마다 사용하는 용어가 다르기 때문에 올바른 도메인 모델을 개발하려면 하위 도메인마다 모델을 만들어야 한다.

    → 모델은 명시적으로 구분되는 경계를 가져서 섞이지 않도록 해야한다.

  • 모델은 특정한 컨텍스트(문맥) 하에서 완전한 의미를 갖는데, 이렇게 구분되는 경계를 갖는 컨텍스트를 바운디드 컨텍스트라고 부른다.

9.2 바운디드 컨텍스트

  • 바운디드 컨텍스트는 모델의 경계를 결정하며, 한 개의 바운디드 컨텍스트는 논리적으로 한 개의 모델을 갖는다.
  • 바운디드 컨텍스트는 실제로 사용자에게 기능을 제공하는 물리적 시스템으로 도메인 모델은 이 바운디드 컨텍스트 안에서 도메인을 구현한다.
  • 이상적으로 하위 도메인과 바운디드 컨텍스트가 일대일 관계를 가지면 좋겠지만 현실은 그렇지 않을 때가 많다. 아래의 예를 살펴보자.
    • 주문 하위 도메인이라도 주문을 처리하는 팀과 복잡한 결제 그액 계산 로직을 구현하는 팀이 따로 있을 수 있다.
    • 카탈로그와 재고 관리가 아직 명확하게 구분되지 않은 경우 두 하위 도메인을 하나의 바운디드 컨텍스트에 구현하기도 한다.
    • 전체 시스템을 한 개 팀에서 구현할 수도 있는데, 이 경우 하위 도메인의 모델이 섞이지 않도록 주의해야 한다.
  • 바운디드 컨텍스트는 구현하는 하위 도메인에 알맞은 모델을 포함한다.
    • 회원의 Member는 애그리거트 루트이지만 주문의 Orderer는 밸류가 된다.
    • 카탈로그의 Product는 상품이 속할 Catetory와 연관을 갖지만, 재고의 Product는 카탈로그의 Category와 연관을 맺지 않는다.

9.3 바운디드 컨텍스트 구현

  • 바운디드 컨텍스트는 도메인 기능을 사용자에게 제공하는 데 필요한 표현 영역, 응용 서비스, 인프라스트럭처 영역을 모두 포함한다.
  • 도메인 모델의 데이터 구조가 바뀌면 DB 테이블 스키마도 함께 변경해야 하므로, 테이블도 바운디드 컨텍스트에 포함된다.
  • 서비스-DAO 구조를 사용하면 도메인 기능이 서비스에 흩어지게 되지만 도메인 기능 자체가 단순하면 서비스-DAO로 구성된 CRUD 방식을 사용해도 코드를 유지 보수하는데 문제가 되지 않는다.
  • 한 바운디드 컨텍스트에서 두 방식을 혼합해서 사용할 수도 있다. 대표적인 예가 CQRS 패턴이다.
  • CQRS는 Common Query Responsibility Segregation의 약자로 상태를 변경하는 명령 기능과 내용을 조회하는 쿼리 기능을 위한 모델을 구분하는 패턴이다.
  • 각 바운디드 컨텍스트는 서로 다른 구현 기술을 사용할 수도 있다.
  • 바운디드 컨텍스트가 반드시 사용자에게 보여지는 UI를 가지고 있어야 하는 것은 아니다.
    • UI를 처리하는 서버를 두고 UI 서버에서 바운디드 컨텍스트와 통신해서 사용자 요청을 처리하는 방법도 있다.
    • 이 구조에서 UI 서버는 각 바운디드 컨텍스트를 위한 파사드 역할을 수행한다.

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

  • 두 바운디드 컨텍스트 간 REST API를 호출하는 것은 직접 통합 방식이다.
  • 두 바운디드 컨텍스트 간 메시지 큐를 사용하는 것은 간접 통합 방식이다.
    • 메시지 큐는 비동기로 메시지를 처리하며, 두 바운디드 컨텍스트는 사용할 메시지의 데이터 구조를 맞춰야 한다.
    • 이 방식은 한쪽에서 메시지를 출판하고 다른 쪽에서 메시지를 구독하는 출판/구독 모델을 따른다.

      // 상품 조회 관련 로그 기록 - 카탈로그 기준으로 작성한 데이터를 큐에 보관
      public class ViewLogService {
          private MessageClient messageClient;
              
          public void appendViewLog(String memberId, String productId, Date time) {
              messageClient.send(new ViewLog(memberId, productId, time));
          }
          
          // messageClinet
          public class RabbitMQClient implements MessageClient {
              private RabbitTemplate rabbitTemplate;
                  
              @Overrride
              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));
          }
          
          // messageClinet
          public class RabbitMQClient implements MessageClient {
              private RabbitTemplate rabbitTemplate;
                  
              @Overrride
              public void send(ActivityLog activityLog) {
                  rabbitTemplate.convertAndSend(logQueueName, activityLog);
              }
              ...
          }
      }
      

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

  • 바운디드 컨텍스트는 어떤 식으로든 연결되기 때문에 두 바운디드 컨텍스트는 다양한 방식으로 관계를 맺는다.
  1. 두 바운디드 컨텍스트 간 관계 중 가장 흔한 관계는 한쪽에서 API를 제공하고, 다른 한 쪽에서는 그 API를 호출하는 관계이다.
    • 상류 컴포넌트는 일종의 서비스 공급자 역할을 하며, 하류 컴포넌트는 그 서비스를 사용하는 고객 역할을 한다.
    • 상류 컴포넌트는 보통 하류 컴포넌트가 사용할 수 있는 통신 프로토콜을 정의하고 이를 공개한다. (REST API, Protocol Buffer ..)
    • 상류 팀의 고객인 하류 팀이 다수 존재하면 상류 팀은 여러 하류 팀의 요구사항을 수용할 수 있는 API를 만들고 이를 서비스 형태로 공개해서 서비스의 일관성을 유지할 수 있다.
      → 이런 서비스를 가리켜 공개 호스트 서비스라고 한다. 공개 호스트 서비스의 대표적인 예가 검색이다.
    • 하류 컴포넌트는 상류 서비스의 모델이 자신의 도메인 모델에 영향을 주지 않도록 보호해 주는 완충 지대를 만들어야 한다.
      → 모델이 깨지는 것을 막아주는 역할을 하기 때문에 안티코럽션 계층이라고도 한다.
          이 계층에서는 두 바운디드 컨텍스트 간의 모델 변환을 처리한다.
  2. 두 바운디드 컨텍스트가 같은 모델을 공유하는 경우도 있는데, 두 팀이 공유하는 모델을 공유 커널이라고 부른다.
  3. 두 바운디드 컨텍스트를 통합하지 않고 서로 독립적으로 발전시키는 것을 독립 방식이라고 한다.
    • 독립 방식에서 두 바운디드 컨텍스트 간의 통합은 수동으로 이루어진다.
    • 수동으로 통합하는 방식은 규모가 커질수록 한계가 있으므로 규모가 커지기 시작하면 바운디드 컨텍스트를 통합해야 한다.
    • 두 바운디드 컨텍스트를 통합하기 위한 별도의 시스템을 만들어야 할 수도 있다.

9.6 컨텍스트 맵

  • 컨텍스트 맵은 바운디드 컨텍스트 간의 관계를 표시한 것으로 시스템의 전체 구조를 보여준다.
  • 컨텍스트 맵은 간단한 도형과 선을 이용할 수 있는 도구를 사용해서 단순하게 그린다.
  • 컨텍트 간의 관계 바뀌면 컨텍스트 맵도 함께 변경해주어야 한다.