https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

도메인 모델과 경계

  • 처음 도메인 모델을 만들때 빠지기 쉬운 함정이 도메인을 완벽하게 표현하는 단일 모델을 만드는 시도를 하는 것이다.
  • 1장에서 말한거처럼 한 도메인은 다시 여러 하위 도메인으로 구분되기 떄문에 한개의 모델로 여러 하위 도메인을 모두 표현하려고 시도하게 되면 모든 하위 도메인에 맞지 않는 모델을 만들게 된다.

 

  • 상품이라는 모델을 생각해보면 카탈로그에서의 상품, 재고 관리에서의 상품, 주문에서의 상품, 배송에서의 상품은 이름만 같지 실제로 의미하는것은 다르다.
  • 카탈로그에서의 상품은 상품 이미지, 상품명, 상품 가격, 옵션목록과 같은 상품정보 위주라면 재고 관리에서의 상품은 실존하는 개별 객체를 추적하기 위한 목적으로 상품을 사용한다.
  • 즉 카탈로그는 물리적으로 한 개인의 상품이 재고 관리에서는 여러 개 존재할 수 있다.

 

  • 논리적으로 같은 존재처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우도 있다.
  • 카탈로그 도메인에서 상품이 검색 도메인에서는 문서로 불리기도 한다.
  • 비슷하게 시스템을 사용하는 사람을 회원 도메인에서 회원이라고 부르지만 주문 도메인에서는 주문자 라고 부르고 배송 도메인에서는 보내는 사람이라 부르기도 한다.
  • 이렇게 하위 도메인마다 같은 용어라도 의미가 다르고 같은 대상이라도 지칭하는 용어가 다를수 있기 떄문에 한개의 모델로 모든 하위 도메인을 표현하려는 시도는 올바른 방법이 아니며 표현할 수도 없다.

 

  • 하위 도메인마다 사용하는 용어가 다르기 떄문에 올바른 도메인 모델을 개발하기 위해서라면 하위 도메인마다 모델을 만들어야 한다.
  • 각 모델은 명시적으로 구분되는 경계를 가져서 섞이지 않도록 해야 한다.
  • 여러 하위도메인의 모델이 섞이기 시작하면 모델의 의미가 약해질뿐 아니라 여러 도메인의 모델이 서로 얽혀 있기 떄문에 각 하위 도메인 별로 다르게 발전하는 요구사항을 모델에 반영하기 어려워 진다.

 

  • 모델은 특정한 컨텍스트(문맥) 하에 완전한 의미를 갖는다.
  • 같은 제품이라도 카탈로그 컨텍스트와 재고 컨텍스트에서 의미가 서로 다르다.
  • 이렇게 구분되는 경계를 갖는 컨텍스트를 DDD에서는 바운디드 컨텍스트 라고 부른다.

 

Bounded Context

  • Bounded Context는 모델의 경계를 결정하며
  • 한 개의 Bounded Context는 논리적으로 한개의 모델을 갖는다.

 

  • Bounded Context는 용어를 기준으로 구분 한다.
  • 카탈로그 컨텍스트와 재고 컨텍스트는 서로 다른 용어를 사용하므로 이 용어를 기준으로 컨텍스트를 분리할수 있다.
  • Bounded Context는 실제로 사용자에게 기능을 제공하는 물리적 시스템으로 도메인 모델은 이 Context안에서 도메인을 구현한다.

 

  • 이상적으로 하위 도메인과 Bounded Context가 1대1 관계를 가지면 좋겠지만 현실은 그렇지 않을 때가 많다.
  • Bounded Context가 기업의 팀 조직 구조에 따라 결정되기도 한다.
  • 예를 들어, 주문 하위 도메인이라도 주문을 처리하는 팀과 복잡한 결제 금액 계산 로직을 구현하는 팀이 따로 있다고 해보자. 이 경우 주문 하위도메인에 주문 Bounded Context와 결제 금액 계산 Bounded Context가 존재하게 된다.
  • 아직 용어를 명확하게 하지 못해 두 하위 도메인을 한 Bounded Context에서 구현하기도 한다.
  • 카탈로그와 재고 관리가 아직 명확하게 구분되지 않은 경우 두 하위 도메인을 한 Bounded Context에서 구현하기도 한다.

 

  • 규모가 작은 기업은 전체 시스템을 한 개 팀에서 구현할 떄도 있다.
  • 예를 들어 소규모 쇼핑몰을 운영할 경우 한개의 웹 애플리케이션으로 온라인 쇼핑을 서비스한다.
  • 이 경우 하나의 시스템에서 회원, 카탈로그, 재고, 구매, 결제와 관련된 기능을 제공 한다. 즉 여러 하위도메인을 한 개의 Bounded Context에서 구현한다

 

  • 여러 하위 도메인을 하나의 Bounded Context에서 개발할때 주의할 점은 하위 도메인의 모델이 뒤섞이지 않도록 하는것이다.
  • 한개의 이클립스 프로젝트에 각 하위 도메인의 모델이 위치하면 아무래도 전체 하위 도메인을 위한 단일 모델을 만들고 싶은 유혹에 빠지기 쉽다.
  • 이런 유혹에 걸려들면 결과적으로 도메인 모델이 개별 하위 도메인을 제대로 반영하지 못해서 하위 도메인별 기능 확장이 어렵게 되고 이는 서비스의 경쟁력을 떨어뜨리는 원인이 된다.
  • 따라서 비록 한개의 Bounded Context에서 여러 하위 도메인을 포함하더라도 하위 도메인마다 구분되는 패키지를 갖도록 구현해야 하위 도메인을 위한 모델이 서로 뒤섞이지 않아서 하위 도메인마다 Bounded Context를 갖는 효과를 낼 수 있다.

 

  • Bounded Context는 도메인 모델을 구분하는 경계 가 되기 때문에 Bounded Context는 구현하는 하위 도메인에 알맞은 모델을 포함한다.
  • 같은 사용자라 하더라도 주문 Bounded Context와 회원 Bounded Context가 갖는 모델이 달라진다.
  • 또한 같은 상품이라도 카탈로그 Bounded Context의 Product와 재고 Bounded Context의 Product는 각 컨텍스트에 맞는 모델을 갖는다.
  • 따라서 회원의 Member는 애그리것 루트이지만 주문의 Orderer는 밸류가 되고 카탈로그의 Product는 상품이 속할 Catergory와 연관을 갖지만 재고의 Product는 카탈로그의 Category와 연관을 맺지 않는다.

 

Bounded Context의 구현

출처: https://minkukjo.github.io/dev/2020/11/11/DDD-09/

 

  • Bounded Context는 도메인 모델 뿐만 아니라 도메인의 기능을 사용자에게 제공하는 데 필요한 표현 영역, 응용 서비스, 인프라 영역등을 모두 포함한다.
  • 도메인 모델의 데이터 구조가 바뀌면 DB 테이블 스키마도 함께 변경해야 하므로 해당 테이블도 Bounded Context에 포함된다.

 

 

  • 표현영역은 인간 사용자를 위하 HTML 페이지를 생성할 수도 있고 다른 Bounded Context를 위해 Rest API를 제공할 수도 있다.

 

출처: https://minkukjo.github.io/dev/2020/11/11/DDD-09/

 

  • 모든 Bounded Context가 반드시 DDD로 개발할 필요는 없다.
  • 상품의 리뷰는 복잡한 도메인 로직을 갖지 않기 때문에 CRUD 방식으로 구현해도 된다.
  • 즉, DAO와 데이터 중심의 밸류 객체를 이용해서 리뷰기능을 구현해도 기능을 유지보수 하는데 큰 문제가 없다.

 

  • 한 Bounded Context에서 두 방식을 혼합해서 사용할 수도 있다.
  • 대표적인 예가 CQRS 패턴이다.
    출처: https://leejaedoo.github.io/bounded_context/
    • CQRS는 상태를 변경하는 명령과, 조회하는 쿼리 기닝을 위한 모델을 구분하는 패턴이다.
    • 이 패턴을 단일 Bounded Context에 적용하면 상태를 변경하는 기능은 도메인 모델 기반으로 구현하고 조회 기능은 서비스-DAO를 이용해서 구현할 수 있다.
  •  
  • 각 Bounded Context는 서로 다른 구현기술을 사용할 수도 있다.
  • 웹 MVC는 스프링 MVC를 사용하고 리포지터리 구현 기술로는 JPA/하이버네이트를 사용하는 Bounded Context가 존재하고
  • Netty를 사용해서 RestAPi를 제공하고 Mybatis를 리포지터리 구현 기술로 사용하는 Bounded Context가 존재할수도 있다.
  • 어떤 Bounded Context는 RDBMS대신 HBase나 몽고 DB와 같은 NoSQL을 사용할 수도 있을 것이다.

 

  • 예를들어 상품상세 정보를 보여주는 페이지를 생각해보면
  • 웹 브라우저는 카탈로그 Bounded Context를 통해 상세 정보를 읽어온 뒤 리뷰 Bounded Context의 Rest API를 직접 호출해서 로딩한 JSON 데이터를 알맞게 가공해서 리뷰 목록을 보여줄 수도 있다.
  • 아니면 UI 를 처리하는 서버를 따루 두고 UI 서버에서 Bounded Context와 통신해서 사용자의 요청을 처리하는 방법도 있다.

 

Bounded Context 간 통합

  • 두팀이 관련된 Bounded Context를 개발하면 자연스럽게 Bounded Context간의 통합이 발생한다.
    • 사용자가 제품 상세 페이지를 볼때 보고있는 상품과 유사한 상품 목록을 하단에 보여준다
    • 위와 같은 조건의 기능이 필요할때 Bounded Context간의 통합이 필요하다.
    • 카탈로그 Bounded Context와 추천 Bounded Context의 통합

 

  • 카탈로그 컨텍스트와 추천 컨텍스트의 도메인 모델은 서로 다르다.
  • 카탈로그는 제품 중심으로 도메인 모델을 구현
  • 추천은 추천 연산을 위한 모델을 구현한다.
  • 예를 들어 추천 시스템은 상품의 상세정보를 포함하지 않으며 상품 번호 대신 아이템 ID라는 용어를 사용해서 식별자를 표현하고 추천 순위와 같은 데이터를 담게 된다.
  • 카탈로그 시스템은 추천 시스템으로부터 추천 데이터를 받아오지만 카탈로그 시스템에서는 추천의 도메인 모델을 사용하기 보다는 카탈로그 도메인을 사용해서 추천 상품을 표현해야 한다.
  • 즉 다음과 같이 카탈로그 모델을 기반으로 하는 도메인 서비스를 이용해서 상품 추천 기능을 표현해야 한다.

 

// 상품 추천 기능을 표현하는 카탈로그 도메인 서비스
public interface ProductRecommendationService {
    public List<Product> getRecommendationsOf(ProductId id);
}

 

  • 도메인 서비스를 구현한 클래스는 인프라스트럭처 영역에 위치한다.
  • 이 클래스는 외부 시스템과의 연동을 처리하고 외부 시스템의 모델과 현재 도메인 모델간의 변환을 책임진다.
  • ( 이거 완전 안티커럽션 레이언데 안티커럽션 레이어란 말을 안한다. )

 

출처: https://leejaedoo.github.io/bounded_context/

 

  • RecSystemClient는 외부 추천 시스템이 제공하는 Rest API를 이용해서 특정 상품을 위한 추천 상품 목록을 로딩한다.
  • 이 Rest API가 제공하는 데이터는 추천 시스템의 모델을 기반으로 하고 있기 떄문에 API 응답은 다음과 같이 상품 도메인 모델과 일치하지 않는 데이터를 제공할 것이다.

 

  • RecSystemClient는 Rest API로부터 데이터를 읽어와 카탈로그 도메인에 맞는 상품 모델로 변환한다.
public class RecSystemClient implements ProductRecommendationService {
    private ProductRepository productRepository;
    
    @Override
    public List<Product> getRecommendationOf(ProductId id) {
        List<RecommendationItem> items = getRecItems(id.getValue());
        return toProducts(items);
    }

    private List<RecommendationItem> getRecItems(String itemId) {
        // externalRecClient는 외부 추천 시스템을 위한 클라이언트라고 가정
        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);
    }
    //...
}

 

  • getRecItems는 exeternalRecClient는 외부 추천 시스템에 연결할 때 사용하는 클라이언트로서 추천 시스템을 관리하는 팀에서 배포하는 모듈이라고 가정하자. ( 어댑터 ? )
  • 이 모듈이 제공하는 RecommendationItem은 추천 시스템의 모델을 따를 것이다.
  • RecSystemClient는 추천 시스템의 모델을 받아와 toProducts() 메서드를 이용해서 카탈로그 도메인의 Product 모델로 변환하는 작업을 처리한다. ( 번역기 )

 

  • 두 모델간의 변환 과정이 복잡하면 번역기만 따로 두기도 한다.
  • Rest API를 통해 직접 두 Bounded Context를 직접 통합 하는 방법이다.
  • 대표적인 간접 통합 방법은 메세지큐를 이용하는것이다.

 

 

  • 카탈로그 Bounded Context는 추천 시스템이 필요로 하는 사용자 활동 이력을 메시지 큐에 추가한다. ( 카탈로그 Bounded Context는 상류 같음 )
  • 메시지 큐는 보통 비동기로 메시지를 처리하기 때문에 카탈로그 Bounded Context 는 메시지를 큐에 추가한 뒤에 추천 Bounded Context가 메시지를 처리할 때까지 기다리지 않고 바로 이어서 자신의 처리를 계속한다.

 

  • 추천 Bounded Context는 큐에서 이력 메시지를 읽어와 추천을 계산하는데 사용할 것이다.
  • 이는 두 Bounded Context가 사용할 메시지의 데이터 구조를 맞춰야 함을 의미.
  • 각각의 Bounded Context를 담당하는 팀은 서로 만나서 주고받을 데이터 형식에 대해 협의해야 한다. ( 협조적인 상류팀 )
  • 메시지 시스템을 카탈로그 측에서 관리하고 있다면 위의 그림과 같이 카탈로그 도메인을 따르는 데이터를 담을것이다.

 

  • 어떤 도메인 관점에서 모델을 사용하느냐에 따라 두 Bounded Context의 구현 코드가 달라지게 된다.
  • 카탈로그 도메인 관점에서 큐에 저장할 메시지를 생성하면 카탈로그 시스템의 연동 코드는 카탈로그 기준의 데이터를 그대로 메싲 큐에 저장한다. ( 이러면 카탈로그가 상류, 추천이 하류 ? )
  • 카탈로그 도메인 모델을 기준으로 메시지를 전송하므로 추천 시스템에서는 자신의 모델에 맞게 메시지를 변환해서 처리해야 한다. ( 카탈로그가 상류, 추천이 하류 )

 

  • 카탈로그 도메인을 메시징 큐에 카탈로그와 관련된 메시지를 저장하고 다른 Bounded Context는 이 큐로부터 필요한 메시지를 수신하는 방식
  • 즉, 한쪽에서 메시지를 출판하고 다른 쪽에서 메시지를 구독하는 출판/구독 (publish subscribe)모델을 따른다.

 

마이크로 서비스와 Bounded Context

  • 마이크로서비스는 애플리케이션을 작은 서비스로 나누어 개발하는 아키텍처 스타일이다.
  • 개별서비스를 독립된 프로세스로 실행하고 각 서비스가 REST API나 메시징을 이용해서 통신하는 구조를 갖는다.
  • 이런 특징은 Bounded Context와 잘 어울린다.
  • 각 Bounded Context는 모델의 경계를 형성하는데 Bounded Context를 마이크로 서비스로 구현하면 자연스럽게 컨텍스트별로 모델이 분리된다.
  • 별도 프로세스로 개발한 Bounded Context는 독립적으로 배포하고 모니터링하고 확장하게 되는데 이 역시 마이크로서비스의 특징이다.

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4ehttps://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4ehttps://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4ehttps://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4ehttps://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4ehttps://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

https://www.notion.so/9-1-fcb6b44a0067439a933d23aa8280ca4e

+ Recent posts