- 처음부터 도메인을 완벽하게 표현하려는 단일 모델을 만드는 시도를 하는 함정에 빠지기 쉽다.
- 한 개의 모델로 여러 하위 도메인을 모두 표현하려고 시도하면 오히려 모든 하위 도메인에 맞지 않는 모델을 만들게 된다.
- 논리적으로 한 개의 모델을 갖는다. 용어를 기준으로 구분한다.
- 실제로 사용자에게 기능을 제공하는 물리적 시스템으로 도메인은 바운디드 컨텍스트 안에서 도메인을 구현한다.
이상적으로 하위 도메인과 바운디드 컨텍스트가 일대일 관계를 가지면 좋겠지만 현실은 그렇지 않을 때가 많다.
- 도메인 모델만 포함하는 것은 아니며, 표현 영역, 응용 서비스, 인프라스트럭쳐 영역을 모두 포함한다. 또한 DB 테이블 스키마도 포함한다.
모든 바운디드 컨텍스트를 반드시 도메인 주도로 개발할 필요는 없다. 복잡한 도메인 로직을 갖지 않는다면 CRUD방식으로 구현해도 된다.
한 바운디드 컨텍스트에서 두 방식을 혼합해서 사용할 수 있다. ex) CQRS: 단일 바운디드 컨텍스트에 적용한다면 상태 변경과 관련된 기능은 도메인 모델 기반으로 구현하고 조회 기능은 서비스 - DAO를 이용해서 구현한다.
서로 다른 구현 기술을 사용할 수 있다.
- 카탈로그 하위 도메인에 개인화 추천 기능을 도입을 한다면 기존 카탈로그 시스템을 개발과 추천 시스템 개발을 병렬로 진행을 할 수 있다.
다른 바운디드 컨텍스트간 통합이 발생할 수 있다.
추천 시스템은 상품의 정보를 포함하지 않으며 상품 번호 대신 아이템 ID라는 용어를 사용해서 식별자를 표현하고 추천 순위와 같은 데이터를 담는다. 즉, 카탈로그의 모델을 기반으로 하는 도메인 서비스를 이용해서 상품 추천 기능을 표현해야 한다.
interface ProductRecommendationService {
fun getRecommendationOf(id: ProductId): List<Product>
}
외부 시스템을 통해 카탈로그 도메인에 맞느 상품 모델로 변환한다.
class RecSystemClient(private val productRepository: ProductRepository): ProductRecommendationService {
override fun getRecommendationOf(id: ProductId): List<Product> {
val items = getRecItems(id.value)
return toProducts(items)
}
private fun getRecItems(itemId: String): List<RecommendationItem> {
// externalRecClient는 외부 추천 시스템에 연결할 때 사용하는 클라이언트로 가정한다.
return extenalRecClient.getRecs(itemId)
}
private fun toProducts(items: List<RecommendationItem>): List<Product> {
return items.stream()
.map { item -> toProductId(item.itemId) }
.map { prodId -> productRepository.findById(prodId) }
.collect(Collection::toList)
}
private fun toProductId(itemId: String): ProductId {
return ProductId(itemId)
}
}
두 모델 간의 변환 과정이 복잡하면 변환 처리를 위한 별도 클래스를 만들고 이 클래스에서 변환을 처리해도 된다.
간접적으로 두 바운디드 컨텍스트를 통합하는 방법도 있다.
ViewLogService
에서는 상품 조회 관련 로그 기록 코드를 남긴다.
class ViewLogService(private val messageClient: MessageClient) {
fun appendViewLog(memberId: String, productId: String, time: LocalDateTime) {
messageClient.send(ViewLog(memberId, productId, time))
}
}
RabbitMQClient
는 메세지를 담을 수 있는 인프라스트럭쳐의 영역이다.
class RabbitMQClient(private val rabbitTemplate: RabbitTemplate): MessageClient {
override fun send(viewLog: ViewLog) {
rabbitTemplate.convertAndSend(logQueueName, viewLog)
}
}
두 바운디드 컨테스트를 개발을 한다면 데이터의 구조를 협의하게 되는데 그 큐를 누가 제공하느냐에 따라 데이터 구조가 결정된다.한쪽에서 메세지를 출판하고 다른 쪽에서 메세지를 구독하는 출판/구독 모델을 따른다.
- 바운디드 컨텍스트는 어떤 식으로든 연결되기 때문에 다양한 방식으로 관계를 맺는다.
공개 호스트 서비스 예시 👆
- 나무만 보고 숲을 보지 못하는 상황을 방지하기 위해선 전체 비즈니스를 조망할 수 있는 지도를 그려야된다.