다음을 통해 공유


마이크로 서비스 도메인 모델 디자인

팁 (조언)

이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공되는 컨테이너화된 .NET 애플리케이션용 .NET 마이크로 서비스 아키텍처인 eBook에서 발췌한 내용입니다.

컨테이너화된 .NET 애플리케이션을 위한 .NET 마이크로서비스 아키텍처 eBook의 표지 썸네일.

각 비즈니스 마이크로 서비스 또는 제한된 컨텍스트에 대해 하나의 풍부한 도메인 모델을 정의합니다.

목표는 각 비즈니스 마이크로 서비스 또는 BC(제한된 컨텍스트)에 대한 단일 응집력 있는 도메인 모델을 만드는 것입니다. 그러나 BC 또는 비즈니스 마이크로 서비스는 때때로 단일 도메인 모델을 공유하는 여러 물리적 서비스로 구성될 수 있습니다. 도메인 모델은 단일 경계 컨텍스트 또는 비즈니스 마이크로 서비스의 규칙, 동작, 비즈니스 언어 및 제약 조건을 캡처해야 합니다.

도메인 엔터티 패턴

엔터티는 도메인 개체를 나타내며 주로 시간 경과에 따른 ID, 연속성 및 지속성뿐만 아니라 도메인 개체를 구성하는 특성에 의해 정의됩니다. Eric Evans는 "주로 ID로 정의된 개체를 엔터티라고 합니다." 엔터티는 모델의 기반이기 때문에 도메인 모델에서 매우 중요합니다. 따라서 신중하게 식별하고 디자인해야 합니다.

엔터티의 ID는 여러 마이크로 서비스 또는 제한된 컨텍스트를 교차할 수 있습니다.

동일한 ID(즉, 동일한 Id 값이며 반드시 같은 도메인 엔티티는 아닐 수도 있음)은 여러 제한된 컨텍스트 또는 마이크로서비스에서 모델링할 수 있습니다. 그러나 동일한 특성과 논리를 가진 동일한 엔터티가 여러 경계 컨텍스트에서 구현된다는 의미는 아닙니다. 대신, 각 제한된 컨텍스트의 엔터티는 해당 특성 및 동작을 해당 제한된 컨텍스트의 도메인에 필요한 특성으로 제한합니다.

예를 들어 구매자 엔터티에는 ID를 포함하여 프로필 또는 ID 마이크로 서비스의 사용자 엔터티에 정의된 대부분의 사용자 특성이 있을 수 있습니다. 그러나 주문 마이크로 서비스의 구매자 엔터티는 특정 구매자 데이터만 주문 프로세스와 관련이 있기 때문에 특성이 적을 수 있습니다. 각 마이크로 서비스 또는 제한된 컨텍스트의 컨텍스트는 도메인 모델에 영향을 줍니다.

도메인 엔터티는 데이터 특성을 구현하는 것 외에도 동작을 구현해야 합니다.

DDD의 도메인 엔터티는 엔터티 데이터(메모리에서 액세스되는 개체)와 관련된 도메인 논리 또는 동작을 구현해야 합니다. 예를 들어 주문 엔터티 클래스의 일부로 주문 항목 추가, 데이터 유효성 검사 및 총 계산과 같은 작업에 대한 메서드로 구현된 비즈니스 논리 및 작업이 있어야 합니다. 엔터티의 메서드는 이러한 규칙이 애플리케이션 계층에 분산되지 않도록, 엔터티의 불변 조건과 규칙을 관리합니다.

그림 7-8에서는 데이터 특성뿐만 아니라 관련 도메인 논리를 사용하는 작업 또는 메서드를 구현하는 도메인 엔터티를 보여 줍니다.

도메인 엔터티의 패턴을 보여 주는 다이어그램

그림 7-8. 데이터와 동작을 구현하는 도메인 엔터티 디자인의 예

도메인 모델 엔터티는 메서드를 통해 동작을 구현합니다. 즉, "빈혈" 모델이 아닙니다. 물론 엔터티 클래스의 일부로 논리를 구현하지 않는 엔터티가 있을 수 있습니다. 대부분의 논리가 집계 루트에 정의되어 있으므로 자식 엔터티에 특수 논리가 없는 경우 집계 내의 자식 엔터티에서 발생할 수 있습니다. 도메인 엔터티 대신 서비스 클래스에서 구현된 논리가 있는 복잡한 마이크로 서비스가 있는 경우 다음 섹션에 설명된 빈혈 도메인 모델에 속할 수 있습니다.

풍부한 도메인 모델 대조 빈약한 도메인 모델

그의 게시물 AnemicDomainModel에서 마틴 파울러는 빈혈 도메인 모델을 다음과 같이 설명합니다.

빈약한 도메인 모델의 기본 증상은 처음 보기에는 실제 모델처럼 보인다는 것입니다. 도메인 공간에 명사 이름을 따서 명명된 개체가 많이 있으며, 이러한 개체는 실제 도메인 모델에 있는 풍부한 관계 및 구조와 연결됩니다. 동작을 살펴보면 이러한 개체에 대한 동작이 거의 없다는 것을 깨닫게 되므로 getter 및 setter의 가방보다 조금 더 많은 동작이 발생합니다.

물론 빈혈 도메인 모델을 사용하는 경우 이러한 데이터 모델은 모든 도메인 또는 비즈니스 논리를 캡처하는 서비스 개체 집합(일반적으로 비즈니스 계층)에서 사용됩니다. 비즈니스 계층은 데이터 모델 위에 있으며 데이터 모델을 데이터로 사용합니다.

빈혈 도메인 모델은 그저 절차적 스타일의 설계입니다. 빈혈 엔터티 개체는 동작(메서드)이 부족하기 때문에 실제 개체가 아닙니다. 데이터 속성만 보유하므로 개체 지향 디자인이 아닙니다. 모든 동작을 서비스 개체(비즈니스 계층)에 배치하면 기본적으로 스파게티 코드 또는 트랜잭션 스크립트가 생성되므로 도메인 모델에서 제공하는 장점이 손실됩니다.

마이크로 서비스 또는 제한된 컨텍스트가 매우 간단한 경우(CRUD 서비스) 데이터 속성만 있는 엔터티 개체 형식의 빈혈 도메인 모델은 충분할 수 있으며 더 복잡한 DDD 패턴을 구현할 가치가 없을 수 있습니다. 이 경우 CRUD 목적으로만 데이터를 사용하여 엔터티를 의도적으로 만들었기 때문에 지속성 모델일 뿐입니다.

따라서 마이크로 서비스 아키텍처는 각 제한된 컨텍스트에 따라 다중 아키텍처 접근 방식에 적합합니다. 예를 들어 eShopOnContainers에서 순서 지정 마이크로 서비스는 DDD 패턴을 구현하지만 간단한 CRUD 서비스인 카탈로그 마이크로 서비스는 구현하지 않습니다.

어떤 사람들은 빈혈 도메인 모델이 안티 패턴이라고 말합니다. 실제로 구현하는 항목에 따라 달라집니다. 만드는 마이크로 서비스가 충분히 간단하다면(예: CRUD 서비스) 빈혈 도메인 모델을 따르는 것은 안티패턴이 아닙니다. 그러나 끊임없이 변화하는 비즈니스 규칙이 많은 마이크로 서비스 도메인의 복잡성을 해결해야 하는 경우 빈혈 도메인 모델은 해당 마이크로 서비스 또는 제한된 컨텍스트에 대한 안티 패턴일 수 있습니다. 이 경우 데이터와 동작을 포함하는 엔터티를 사용하여 풍부한 모델로 디자인하고 추가 DDD 패턴(집계, 값 개체 등)을 구현하면 이러한 마이크로 서비스의 장기적인 성공에 큰 이점이 있을 수 있습니다.

추가 리소스

Value 개체 패턴

에릭 에반스가 지적했듯이, "많은 개체에는 개념적 ID가 없습니다. 이러한 개체는 사물의 특정 특성을 설명합니다."

엔터티에는 ID가 필요하지만 시스템에는 Value Object 패턴과 같이 그렇지 않은 개체가 많이 있습니다. 값 개체는 도메인 측면을 설명하는 개념 ID가 없는 개체입니다. 이러한 개체는 일시적으로만 관련된 디자인 요소를 나타내기 위해 인스턴스화하는 개체입니다. 당신은 그들이 누구인지 가 아니라 그들이 무엇인지 에 관심이 있습니다. 숫자와 문자열을 예로 들어 있지만 특성 그룹과 같은 상위 수준 개념일 수도 있습니다.

마이크로 서비스의 엔터티인 항목은 다른 마이크로 서비스의 엔터티가 아닐 수 있습니다. 두 번째 경우 제한된 컨텍스트는 다른 의미를 가질 수 있기 때문입니다. 예를 들어 전자 상거래 애플리케이션의 주소는 개인 또는 회사에 대한 고객 프로필의 특성 그룹만 나타낼 수 있으므로 ID가 전혀 없을 수 있습니다. 이 경우 주소는 값 개체로 분류되어야 합니다. 그러나 전기 전력 유틸리티 회사의 애플리케이션에서 고객 주소는 비즈니스 도메인에 중요할 수 있습니다. 따라서 청구 시스템을 주소에 직접 연결할 수 있도록 주소에 ID가 있어야 합니다. 이 경우 주소는 도메인 엔터티로 분류되어야 합니다.

이름과 성을 가진 사람은 이름 및 성도 다른 사람을 참조하는 경우와 같이 다른 값 집합과 일치하는 경우에도 ID를 가지고 있기 때문에 일반적으로 엔터티입니다.

값 개체는 관계형 데이터베이스 및 EF(Entity Framework)와 같은 ORM에서 관리하기 어려운 반면 문서 지향 데이터베이스에서는 구현하고 사용하기가 더 쉽습니다.

EF Core 2.0 이상 버전에는 나중에 자세히 살펴보겠습니다. 값 개체를 보다 쉽게 처리할 수 있는 소유 엔터티 기능이 포함되어 있습니다.

추가 리소스

집계 패턴

도메인 모델에는 주문 처리 또는 인벤토리와 같은 중요한 기능 영역을 제어할 수 있는 다양한 데이터 엔터티 및 프로세스의 클러스터가 포함됩니다. 보다 세분화된 DDD 단위는 응집력 있는 단위로 처리할 수 있는 엔터티 및 동작의 클러스터 또는 그룹을 설명하는 집계입니다.

일반적으로 필요한 트랜잭션에 따라 집계를 정의합니다. 클래식 예제는 주문 항목 목록도 포함하는 주문입니다. 주문 항목은 일반적으로 개체로 나타납니다. 그러나 주문 엔터티를 루트 엔터티로 포함하는 주문 집계 내에서 자식 엔터티가 됩니다. 이 루트 엔터티는 일반적으로 집계 루트라고 합니다.

집계 식별은 어려울 수 있습니다. 집계는 함께 일치해야 하는 개체 그룹이지만 개체 그룹을 선택하고 집계에 레이블을 지정할 수는 없습니다. 도메인 개념으로 시작하고 해당 개념과 관련된 가장 일반적인 트랜잭션에 사용되는 엔터티를 고려해야 합니다. 트랜잭션적으로 일관되어야 하는 엔터티가 집계를 형성합니다. 트랜잭션 작업에 대해 생각하는 것이 집계를 식별하는 가장 좋은 방법일 것입니다.

집계 루트 또는 루트 엔터티 패턴

집계는 하나 이상의 엔터티, 즉 루트 엔터티 또는 기본 엔터티라고도 하는 집계 루트로 구성됩니다. 또한 필요한 동작 및 트랜잭션을 구현하기 위해 모든 엔터티와 개체가 함께 작동하는 여러 자식 엔터티 및 값 개체를 가질 수 있습니다.

집계 루트의 목적은 집계의 일관성을 보장하는 것입니다. 집계 루트 클래스의 메서드 또는 작업을 통해 집계에 대한 업데이트의 유일한 진입점이어야 합니다. 집계 루트를 통해서만 집계 내의 엔터티를 변경해야 합니다. 집계 내에서 준수해야 하는 모든 불변성과 일관성 규칙을 고려하여, 집계의 일관성을 지키는 보호자입니다. 자식 엔터티 또는 값 개체를 독립적으로 변경하는 경우 집계 루트는 집계가 유효한 상태인지 확인할 수 없습니다. 그것은 느슨한 다리를 가진 테이블처럼 될 것입니다. 일관성 유지 관리가 집계 루트의 주요 목적입니다.

그림 7-9에서는 단일 엔터티(집계 루트 구매자)를 포함하는 구매자 집계와 같은 샘플 집계를 볼 수 있습니다. 순서 집계에는 여러 엔터티와 값 개체가 포함됩니다.

구매자 집계 및 주문 집계를 비교하는 다이어그램

그림 7-9. 여러 개 또는 단일 엔터티가 있는 집합체의 예

DDD 도메인 모델은 집계로 구성되고, 집계는 엔터티 하나 이상을 가질 수 있으며 값 개체도 포함할 수 있습니다. 구매자 집계는 eShopOnContainers 참조 애플리케이션의 주문 마이크로서비스에서처럼 도메인에 따라 추가 하위 엔터티를 가질 수 있습니다. 그림 7-9는 구매자가 단일 엔터티를 갖는 경우를 집계 루트만 포함하는 집계의 예로 보여 줍니다.

집계의 분리를 유지하고 그 간에 명확한 경계를 유지하기 위해 DDD 도메인 모델에서 eShopOnContainers의 Ordering 마이크로 서비스 도메인 모델에 구현된 대로 집계 간 직접 탐색을 허용하지 않고 외래 키(FK) 필드만 갖는 것이 좋습니다. 주문 엔터티에는 구매자에 대한 외래 키 필드만 있지만 다음 코드와 같이 EF Core 탐색 속성은 없습니다.

public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId; // FK pointing to a different aggregate root
    public OrderStatus OrderStatus { get; private set; }
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    // ... Additional code
}

집계를 식별하고 작업하려면 연구와 경험이 필요합니다. 자세한 내용은 다음 추가 리소스 목록을 참조하세요.

추가 리소스