도메인 주도 설계 (Domain Driven Design)란 무엇인가?
Intro
MSA 컨셉을 생각하면 비즈니스 도메인 중심으로 서비스를 모델링하고 구현하는 것이 중요하다. 이 때 각각의 복잡한 도메인을 모델링하고 표현력있게 설계하는 것을 도메인 주도 설계(DDD) 라고 한다.
Layer 구조
레이어간의 참조 관계에서는 단방향 의존을 유지하고 계층간 호출에서는 인터페이스를 통한 호출이 되도록 한다.
Layer 별 특징과 역할
1. Interface
- 사용자에게 정보를 보여주고 사용자의 명령을 해석 해야하는 책임이 있다.
- Controller, DTO, Mapper
2. Application (응용 계층)
- 수행할 작업을 정의하고 표현력 있는 도메인 객체가 문제를 해결하게 한다.
- 이 계층에서 책임지는 작업은 업무상 중요하거나 다른 시스템의 응용 계층과 상호 작용하는 데 필요한 것들이다.
- 이 계층은 얅게 유지되고 오로지 작업을 조정하고 아래에 위치한 계층에 포함된 도메인 객체 협력자에게 작업을 위임한다.
- Facade
3. Domain
- 업무 개념과 업무 상황에 대한 정보, 업무 규칙을 표현하는 일을 책임진다.
- 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며, 그와 같은 상태 저장과 관련된 기술적인 세부사항은 Infrastructure 에 위임 한다.
- 도메인은 업무용 소프트웨어의 핵심
- Entity, Service, Command, Criteria, Info, Executor, Reader, Store
4. Infrastructure
- 상위 계층을 지원하는 일반화된 기술적 기능을 제공한다.
- 이러한 기능에는 Application에 대한 메시지 전송, 도메인 영속화 등이 있다.
- Low Level 구현체 (StoreImpl, ReaderImpl …)
Layer 간 참조 관계
- Layer 간의 참조 관계에서 Application 과 Infrastructure 는 Domain Layer 를 바라보게하고 양방향 참조는 허용하지 않게 한다.
- Domain Layer 는 Low Level 의 기술에 상관없이 독립적으로 존재할 수 있어야 한다.
- 이를 위해 대부분의 주요 로직은 추상화되고, Runtime 시에는 DIP 개념을 활용하여 실제 구현체가 동작하게 한다.
Layer 별 구현 상세
1. Domain Layer
DDD 에서 말하는 domain layer 의 역할은 다음과 같다
- 업무 개념과 업무 상황에 대한 정보, 업무 규칙을 표현하는 일을 책임진다
- 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며 그와 같은 상태 저장과 관련된 기술적인 세부사항은 인프라 스트럭쳐에 위임한다.
- 즉, 도메인 계층이 업무용 소프트웨어의 핵심이다
DDD 의 목표는 기술보다는 도메인에 대한 모델에 집중해 더 나은 소프트웨어를 만들어내는 것이다. DDD 에서 도메인 모델을 정의하고 구현하는 layer 는 domain layer 이기 때문에 DDD 에서는 domain layer 가 핵심이다.
Domain Layer 표준 구현
- Domain layer 에서의 Service 에서는 해당 도메인의 전체 흐름을 파악할 수 있도록 구현되어야 하기 때문에 추상화 레벨을 많이 높혀야 한다.
- 세세한 기술 구현은 Service 가 아니라 Infrastructure 의implements 클래스에 위임하고, Service 에서는 이를 활용하기 위한 interface 를 선언하고 사용한다.
- DIP 를 활용하여 도메인이 사용하는 interface 의 실제 구현체를 주입 받아 (injection) 사용할 수 있도록 한다.
- 이런 식의 구현을 가져가면 service 의 메서드를 읽기만해도 업무 도메인의 흐름을 대략적으로 파악이 가능하고 interface 로 추상화된 실제 구현 기술은 언제든지 원하는 것으로 교체가 가능하게 된다.
- domain layer 에서의 모든 클래스명이 “xxxxService” 로 선언될 필요는 없다 하나의 도메인 패키지 내에 수많은 Service 클래스가 존재하게 되면, 도메인 전체 의 흐름을 컨트롤하는 Service 가 무엇인지 알기 어렵기 때문이다. - xxxReader - xxxStore - xxxExecutor - xxxFactory - xxxAggregator
- Service 간에는 참조 관계를 가지지 않도록 한다. - DDD 의 Aggregate Root 개념을 알고 있다면 도메인 내의 Entity 간에도 상하 관 계가 명확히 생긴다는 것을 알게 된다 - Service 간에는 참조 관계를 가지지 않도록 원칙을 세워야한다.
2. Infrastructure Layer
DDD 에서 말하는 Infrastructure layer 의 역할은 “상위 계층을 지원하는 일반화된 기술적 기능을 제공”이다.
- domain layer 에 선언되고 사용되는 추상화된 interface 를 실제로 구현하여 runtime 시에는 실제 로직이 동작하게 하기 위해 DIP 개념을 활용한다.
- 세세한 기술 스택을 활용하여 domain 의 추상화된 interface 를 구현하는 것이므로 비교적 구현에서의 자유도를 높게 가져갈 수 있다.
- Service 간의 참조 관계는 막았지만, Infrastructure layer 에서의 구현체 간에는 참조관계를 허용한다.
- Infrastructure 에서의 구현체는 domain layer 에 선언된 interface 를 구현하는 경우가 대부분이므로 Service 에 비해 의존성을 많이 가지지 않게 된다.
- 로직의 재활용을 위해 Infrastructure 내의 구현체를 의존 관계로 활용해도 된다.
- @Component 를 활용하여 명시적인 의미를 부여하여 Servcie 계층과 구분한다.
3. Application Layer
다른 애플리케이션 계층과의 상호 작용을 하여 도메인 객체가 문제를 해결하도록 지시
- 작업을 조정하기만 하고 도메인 상태를 가지면 안 된다.
- 그렇다면 수행할 작업을 정의하고 작업을 조정하는게 결국 도메인 로직 아닌가?
- transaction 으로 묶여야 하는 도메인 로직과 그 외의 로직을 aggregation 하는 역할로 한정 짓기 때문에 도메인 로직과 구분될 수 있다.
- 실제적인 주문 요구사항을 예시로 Facade 구현을 정의해보자면 다음과 같다.
- “주문완료 후 유저에게 카카오톡으로 주문 성공 알림이 전달된다” 라는 요구사항
- 그러나 주문 완료 직후의 카카오톡 알림 발송이 실패하더라도, 주문 로직이 전체 롤백될 필요는 없다.
- 단순한 알림 발송의 실패 일뿐 고객은 메인 서비스를 통해 주문 완료를 확인 할 수 있다.
- 이런맥락으로 Facede 내에서 주문 완료 메서드를 구현하면 아래와 같을 것이다.
1
2
3
4
public String completeOrder(OrderCommand.RegisterOrder registerOrder){
var orderToken: String = orderService.completeOrder(registerOrder);
return orderToken
}
- Facade 안의 completeOrder 메서드에는 transaction 을 선언하지 않았다. - orderService.completeOrder(registerOrder) 내에는 transaction 이 선 언되어 있고, 주문완료 처리 중에 예외가 발생하면 Order Aggregate 전체 데 이터가 rollback 이 된다. - orderService.completeOrder(registerOrder) 가 성공하고 notificationService.sendKakao() 가 실패하더라도, 주문완료 처리는 rollback 되지 않는다.
- Order Aggregate 의 정합성은 지키면서도, 주요 도메인 로직에는 포함되지 않는 외부 서비스 call (여기서는 카카오톡 알림 발송) 은 성공 / 실패에 크게 민 감하지 않게 요구사항을 처리하게 된다.
4. Interfaces Layer
사용자에게 정보를 보여주고 사용자의 명령을 해석하는 책임
- API 를 설계할 때에는 없어도 되는 Request Parameter 는 제거하고, 외부에 리턴하 는 Response 도 최소한을 유지한다. - 요구하는 Request Parameter 가 많다는 것은 관련된 메서드나 객체에서 처리해야 하는 로직이 많다는 것을 의미하고, 이는 관련된 객체가 생각보다 많은 역할을 하고 있다는 신호이다. - Response 의 경우도 불필요한 응답을 제공하고 있고 이를 가져다 쓰는 외부 로직이 있다면, 추후 해당 Response 에서 특정 프로퍼티는 제거하기 어렵게 될 수 있다.
- http, gRPC, 비동기 메시징과 같은 서비스간 통신 기술은 Interfaces layer 에서만 사용되는 것이 좋다.
- 언제든지 변하고 교채될 수 있는 외부 통신 기술로 인해 Domain 로직이 변경되어야 하는 상황이 발생될 수 있다.
This post is licensed under CC BY 4.0 by the author.