DDD 지향 마이크로 서비스 디자인

이 콘텐츠는 eBook, 컨테이너화된 .NET 애플리케이션을 위한 .NET 마이크로 서비스 아키텍처에서 발췌한 것이며, .NET 문서에서 제공되거나 오프라인 상태에서도 읽을 수 있는 PDF(무료 다운로드 가능)로 제공됩니다.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

DDD(도메인 기반 설계)는 사용 사례와 관련하여 현실의 비즈니스에 근거한 모델링을 대표합니다. 애플리케이션 빌드의 컨텍스트에서 DDD는 문제를 도메인으로 설명합니다. 경계가 정해진 컨텍스트로 독립적인 문제 영역을 설명하며(각 바인딩된 컨텍스트가 마이크로 서비스에 상관됨) 문제 해결에 관해 설명하는 공통 언어를 강조합니다. 내부 구현 지원을 위해 많은 모델(빈약한 도메인 모델 없음), 값 개체, 집계, 집계 루트(또는 루트 엔터티) 규칙이 있는 도메인 엔터티처럼 여러 기술적 개념과 패턴도 제시합니다. 이 섹션에서는 이러한 내부 패턴의 설계와 구현을 소개합니다.

경우에 따라 이러한 DDD 기술 규칙과 패턴이 DDD 방법의 구현을 위한 학습에 장애처럼 보이기도 합니다. 그러나 중요한 것은 패턴 자체가 아니라 비즈니스 문제에 맞게 코드를 구성하고 동일한 비즈니스 용어(유비쿼터스 언어)를 사용하는 것입니다. 또한 DDD 방법은 중대한 비즈니스 규칙이 있는 복잡한 마이크로 서비스를 구현할 때만 적용해야 합니다. CRUD 서비스처럼 더 간단한 업무는 더 간단한 방법으로 관리할 수 있습니다.

마이크로 서비스를 설계 및 정의할 때는 그 경계를 정하는 것이 핵심입니다. DDD 패턴은 도메인의 복잡성을 이해하는 데 도움이 됩니다. 경계가 지정된 각 컨텍스트에 대한 도메인 모델의 경우 도메인을 모델링하기 위한 엔터티, 값 개체 및 집계를 식별하여 정의합니다. 컨텍스트를 정의하는 경계 안에 포함된 도메인 모델을 빌드하여 구체화합니다. 그리고 마이크로 서비스의 형태로 명시적입니다. 일부 경우 BC나 비즈니스 마이크로 서비스가 몇 가지 물리적 서비스로 구성될 수 있으나 이러한 경계 내 구성 요소는 마이크로 서비스로 마무리됩니다. DDD는 경계에 관한 것이며 마이크로 서비스도 마찬가지입니다.

마이크로 서비스 컨텍스트 경계를 상대적으로 작게 유지

경계가 지정된 컨텍스트 사이에서 어디를 경계로 할 것인지 결정하는 것은 경합하는 두 목표의 균형을 맞추는 것입니다. 먼저 가능한 가장 작은 마이크로 서비스를 만들려고 합니다. 이것이 주 목표는 아니며 응집이 필요한 항목 주변의 경계를 만들어야 합니다. 두 번째로 마이크로 서비스 간의 자잘한 통신은 지양하고자 합니다. 이 두 목표는 서로 상충할 수 있습니다. 추가적으로 경계가 지정된 새 컨텍스트를 분리할 때마다 매번 통신 경계가 신속하게 증대하는 수준이 되도록 시스템을 가능한 작은 규모의 여러 마이크로 서비스로 나누어 그 균형을 맞추어야 합니다. 응집력은 경계가 지정된 단일 컨텍스트에서 핵심입니다.

클래스를 구현할 때 부적절한 관계 코드 분위기와 유사합니다. 두 마이크로 서비스가 서로 협력하는 일이 많을 경우 동일한 마이크로 서비스가 되어야 할 수 있습니다.

이 측면을 살펴보는 또 다른 방법은 자율성입니다. 마이크로 서비스가 다른 서비스에 의존하여 요청을 직접 서비스해야 할 경우 진정한 자율성이라 할 수 없습니다.

DDD 마이크로 서비스의 계층

비즈니스 및 기술 복잡성이 높은 대부분의 엔터프라이즈 애플리케이션은 여러 계층으로 정의됩니다. 계층은 논리적 아티팩트로, 서비스 배포와는 관련이 없습니다. 이들은 개발자가 코드에서 복잡성을 관리하는 데 도움이 되기 위한 것입니다. 서로 다른 계층(예: 도메인 모델 계층 및 프레젠테이션 계층)은 형식이 서로 다를 수 있고 이로 인해 형식 간 변환이 필요하게 됩니다.

예를 들어 데이터베이스에서 엔터티를 로드할 수 있습니다. 그런 다음, 이 정보의 일부나, 다른 엔터티의 추가 데이터를 포함한 정보의 집계를 REST Web API를 통해 클라이언트 UI로 보낼 수 있습니다. 여기서의 핵심은 도메인 개체가 도메인 모델 계층 안에 포함된다는 점이며 프레젠테이션 계층처럼 자신이 속하지 않은 다른 영역으로 전파되서는 안 됩니다.

또한 집계 루트(루트 엔터티)에서 제어되는 항상 유효한 엔터티(도메인 모델 계층의 유효성 검사 설계 섹션 참조)가 필요합니다. 따라서 엔터티는 클라이언트 뷰에 바인딩되어서는 안됩니다. UI 수준에서는 일부 데이터가 여전히 유효성 검사되지 않을 수 있기 때문입니다. 이러한 이유로 ViewModel이 필요합니다. ViewModel은 프레젠테이션 계층에만 필요한 데이터 모델입니다. 도메인 엔터티는 ViewModel에 직접 속해 있지 않습니다. 대신 ViewModel과 도메인 엔터티 사이에 상호 변환이 필요합니다.

복잡성을 해결할 때는 도메인 모델을 엔터티 그룹(집계)과 관련한 모든 고정 및 규칙이 단일 진임 지점 또는 게이트인 집계 루트를 통해 수행되도록 하는 집계 루트에서 제어하는 것이 중요합니다.

그림 7-5는 eShopOnContainers 애플리케이션에서 계층화된 디자인을 구현하는 방법을 보여줍니다.

Diagram showing the layers in a domain-driven design microservice.

그림 7-5. eShopOnContainers 주문 마이크로 서비스의 DDD 계층

Ordering과 같은 DDD 마이크로 서비스의 세 계층. 각 계층은 VS 프로젝트로 애플리케이션 계층은 Ordering.API, 도메인 계층은 Ordering.Domain, 인프라 계층은 Ordering.Infrastructure입니다. 각 계층이 특정 다른 계층과만 통신하도록 시스템을 설계하려 합니다. 계층이 다양한 클래스 라이브러리로 구현된다면 라이브러리 간에 어떠한 종속성이 설정되어 있는지 분명히 파악할 수 있기 때문에 해당 접근 방식을 더 쉽게 적용할 수 있습니다. 예를 들어, 도메인 모델 계층은 다른 계층에 대해 종속성이 있어서는 안됩니다(도메인 모델 클래스는 일반 기존 클래스 개체 또는 POCO 클래스여야 함). 그림 7-6에서 보듯이 Ordering.Domain 계층 라이브러리에는 .NET 라이브러리 또는 NuGet 패키지에 대한 종속성만 있고 데이터 라이브러리나 지속 라이브러리 같은 다른 사용자 지정 라이브러리에 대한 종속성은 없습니다.

Screenshot of Ordering.Domain dependencies.

그림 7-6. 라이브러리로 구현되는 계층은 레이어 간의 종속성을 더 잘 제어할 수 있습니다.

도메인 모델 계층

Eric Evans는 훌륭한 저서인 Domain Driven Design에서 도메인 모델 게층과 애플리케이션 계층을 따르는 것에 대해 설명한 바 있습니다.

도메인 모델 계층: 비즈니스 개념, 비즈니스 상황 정보, 비즈니스 규칙을 나타내는 작업을 담당합니다. 저장을 위한 기술 세부 정보는 인프라에 위임되지만 비즈니스 상황을 반영한 상태가 여기서 제어 및 사용됩니다. 이 계층은 비즈니스 소프트웨어의 핵심입니다.

도메인 모델 계층은 비즈니스가 표현되는 부분입니다. .NET에서 마이크로 서비스 도메인 모델 계층을 구현할 때 해당 계층은 데이터와 동작(논리가 있는 메서드)을 수집하는 도메인 엔터티가 있는 클래스 라이브러리로 코딩됩니다.

지속성 무시인프라 무시 원칙에 따라 이 계층은 완전히 데이터 지속성 세부 정보를 무시해야 합니다. 이러한 지속성 작업은 인프라 계층에서 수행되어야 합니다. 따라서 이 계층은 인프라와 직접적인 종속성을 취하지 않습니다. 즉 도메인 모델 엔터티 클래스가 POCO여야 하는 것이 중요한 규칙이 됩니다.

도메인 엔터티는 Entity Framework 또는 NHibernate 같은 데이터 액세스 인프라 프레임워크에 직접적인 종속성(기준 클래스에서 파생 등)을 갖지 않아야 합니다. 이상적으로 도메인 엔터티는 인프라 프레임워크에서 정의된 어떤 유형으로부터 파생되거나 그러한 유형을 구현해서는 안 됩니다.

Entity Framework Core와 같은 대부분의 최신 ORM 프레임워크에서는 이 방식을 허용하므로 도메인 모델 클래스가 인프라와 연결되지 않습니다. 그러나 Azure Service Fabric에서의 Actors 및 Reliable Collections 같이 특정 NoSQL 데이터베이스와 프레임워크를 사용할 경우, POCO 엔터티가 항상 가능한 것은 아닙니다.

도메인 모델에 대해 지속성 무시 원칙을 따르는 것이 중요하다 하더라도 지속성 우려를 무시해서는 안 됩니다. 물리적 데이터 모델과 이 모델이 엔터티 개체 모델에 매핑되는 방식을 이해하는 것은 여전히 중요합니다. 그렇지 않으면 불가능한 설계를 만들게 될 수 있습니다.

또한 이 측면은 관계형 데이터베이스용으로 설계된 모델을 취하여 직접 NoSQL이나 문서 중심 데이터베이스로 이동할 수 있다는 뜻은 아닙니다. 일부 엔터티 모델에서는 이 모델이 맞을 수 있지만 보통은 그렇지 않습니다. 스토리지 기술과 ORM 기술에 모두 근거하여 엔터티 모델이 준수해야 하는 제약이 여전히 존재합니다.

애플리케이션 계층

애플리케이션 계층을 시작하면서 Eric Evans의 저서 Domain Driven Design을 다시 한 번 살펴보겠습니다.

애플리케이션 계층: 소프트웨어가 수행할 작업을 정의하고 표현적 도메인 개체가 문제를 해결하도록 지시합니다. 이 계층이 담당하는 작업은 비즈니스에 유의미하거나, 다른 시스템의 애플리케이션 계층과의 상호 작용에 필요합니다. 이 계층은 가볍게 유지됩니다. 비즈니스 규칙이나 지식은 포함하지 않으며 작업을 조정하고 다음 하위 계층에서 도메인 계체의 협력을 위해 업무를 위임하기만 합니다. 비즈니스 상환을 반영하는 상태를 갖지 않지만 사용자 또는 프로그램에 대해 작업의 진행 상황을 반영하는 상태는 가질 수 있습니다.

.NET에서 마이크로 서비스의 애플리케이션 계층은 일반적으로 ASP.NET Core Web API 프로젝트로 코딩됩니다. 프로젝트는 마이크로 서비스의 상호 작용, 원격 네트워크 액세스 및 UI나 클라이언트 앱에서 사용하는 외부 Web API를 구현합니다. 여기에는 CQRS 방식을 사용할 경우 쿼리, 마이크로 서비스에서 수락되는 명령, 마이크로 서비스 간의 이벤트 중심 통신(통합 이벤트)도 포함됩니다. 애플리케이션 계층을 나타내는 ASP.NET Core Web API는 비즈니스 규칙이나 도메인 정보(특히 트랜잭션이나 업데이트에 대한 도메인 규칙)를 포함해서는 안 됩니다. 이런 것은 도메인 모델 클래스 라이브러리가 소유해야 합니다. 애플리케이션 계층은 작업을 조정하기만 하며 도메인 상태를 보유하거나 정의해서는 안 됩니다(도메인 모델). 비즈니스 규칙 실행을 도메인 모델 클래스 자체에 위임하며(집계 루트 및 도메인 엔터티) 결과적으로 도메인 엔터티 내에서 데이터를 업데이트합니다.

기본적으로 애플리케이션 논리에서 특정 프런트 엔드에 종속되는 모든 사용 사례를 구현하게 됩니다. 예를 들어, Web API에 연결된 구현입니다.

도메인 모델 계층의 도메인 논리, 고정, 데이터 모델, 관련 비즈니스 규칙이 프레젠테이션 및 애플리케이션 계층에서 완전히 독립적이 되게 하는 것이 목표입니다. 무엇보다 도메인 모델 계층은 인프라 프레임워크에 직접적으로 종속되어서는 안 됩니다.

인프라 계층

인프라 계층은 최초에 도메인 엔터티에 있던 데이터(메모리 내)가 데이터베이스나 기타 영구 저장소에 유지되는 방식입니다. Entity Framework Core 코드를 사용하여 관계형 데이터베이스에 데이터를 지속하는 데 DBContext를 사용하는 저장소 패턴 클래스를 구현하는 것을 예로 들 수 있습니다.

앞에서 언급한 지속성 무시인프라 무시 원칙에 따라 인프라 계층은 도메인 모델 계층을 “오염” 시키지 않아야 합니다. 프레임워크에 대한 고정 종속성을 지양함으로써 도메인 모델 엔터티 클래스는 데이터 유지에 사용되는 인프라(EF 또는 기타 프레임워크)와는 무관하게 유지해야 합니다. 도메인 모델 계층 클래스 라이브러리에는 자체 도메인 코드인 소프트웨어의 핵심을 구현하는 POCO 엔터티 소프트웨어만 있어야 하며 인프라 기술과는 완전히 분리되어야 합니다.

따라서 그림 7-7에서와 같이 계층 또는 클래스 라이브러리와 프로젝트는 결과적으로 도메인 모델 계층(라이브러리)에 종속되어야 하며 그 반대의 경우는 아닙니다.

Diagram showing dependencies that exist between DDD service layers.

그림 7-7. DDD의 계층 간 종속성

DDD 서비스의 종속성, 애플리케이션 계층은 도메인 및 인프라에 따라 다르며 인프라는 도메인에 종속되지만 도메인은 모든 계층에 종속되지 않습니다. 이 계층 설계는 각각의 마이크로 서비스마다 독립적입니다. 앞서 언급한 것처럼 DDD 패턴에 따라 가장 복잡한 마이크로 서비스를 구현할 수 있고 더 간단한 데이터 중심 마이크로 서비스(단일 계층의 간단한 CRUD)는 더 간단한 방법으로 구현합니다.

추가 리소스