DDD 도메인 모델, 아키텍처
도메인 모델
- 특정 도메인을 개념적으로 표현한 것.
- 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴.
- 도메인 계층을 구현할 때 사용하는 객체 모델을 언급할 때 사용됨.
도메인 모델 도출
도메인 모델을 도출할 때 가장 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것
문서화
문서화를 하는 주된 이유는 지식을 공유하기 위함이다. 코드 자체도 문서화의 대상이 된다. 단순히 코드를 보기 좋게 작성하는 것뿐만 아니라 도메인 관점에서 코드가 도메인을 잘 표현해야 가독성이 높아지고 문서로서 코드가 의미를 갖는다.
엔티티와 밸류
이 둘의 차이를 명확하게 이해하는 것은 도메인 구현에 있어 중요하다.
Entity
- 식별자를 가짐.
- 식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 가짐.
Value
- 개념적으로 완전한 하나를 표현할 때 사용.
- 의미를 좀 더 명확하게 나타낼 수 있음.
- 데이터 변경 기능을 제공하지 않는 불변 타입으로 표현.
- 불변 객체로서 참조 투명성과 스레드 안정성을 가짐.
// 기존
public class OrderLine {
private Product product;
private int price;
private int quantity;
private int amounts;
}
--------------------------------------------------------------
// 변경
// 밸류타입으로 price, amounts 의미를 더 명확하게 나타냄.
public class OrderLine {
private Product product;
private Money price;
private int quantity;
private Money amounts;
}
public class Money {
private int value;
...
// int 대신 Money 타입으로 정의하면 돈 계산을 위한 기능을 추가할 수 있다.
public Money add(Money money) {
return new Money(this.value + money.value);
}
}
엔티티 식별자와 밸류 타입
엔티티 식별자의 실제 데이터는 String과 같은 문자열로 구성된 경우가 많다. 이 때 단순 문자열 대신 밸류 타입을 사용하면 의미를 더 잘 드러낼 수 있다.
public class Order {
// String 대신 OrderNo 타입을 사용
private OrderNo id;
...
}
도메인 모델에서는 set 메서드 넣지 않기
- set 메서드는 도메인의 핵심 개념이나 의도를 사라지게 한다.
- set 메서드를 사용하면 도메인 객체를 생성할 때 온전하지 않은 상태가 될 수 있다.
- 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성자를 통해 필요한 데이터를 받아야 한다. 생성자 호출 시점에 필요한 데이터가 올바른지 검사할 수도 있음.
아키텍처
표현 영역
- http 요청을 응용 영역이 필요로 하는 형식으로 변환해서 응용 영역에 전달 및 http 응답을 변환하여 전송
응용 영역
- 시스템이 사용자에게 제공해야 할 기능을 구현.
- 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.
도메인 영역
- 도메인 모델을 구현.
인프라스트럭처 영역
- 논리적인 개념을 표현하기 보다는 실제 구현을 다룸.
- DBMS 연동, 메시징 큐 메시지 전송/수신, 몽고DB나 Redis와의 데이터 연동 처리, SMTP를 이용한 메일 발송, http 클라이언트를 이용해서 REST API 호출 등
계층 구조
- 각 계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다. 예를 들어 표현, 응용 계층은 도메인 계층에 의존하지만 그 반대는 성립하지 않음.
DIP(의존 역전 원칙)
- 고수준 모듈(도메인 영역)이 제대로 동작하려면 저수준 모듈(인프라스트럭처 영역)을 사용해야 한다. 그런데 고수준 모듈이 저수준 모듈을 사용하면 구현 변경과 테스트가 어렵다. DIP는 이를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다.
- 고수준 모듈이 저수준 모듈에 의존하지 않고 구현을 추상화한 인터페이스에 의존하게 한다. 실제 사용할 저수준 구현 객체는 의존 주입을 통해 전달 받는다.
- 대표적으로 스프링과 같은 프레임워크가 존재함.
- DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출해야 한다.
도메인 영역 주요 구성요소
요소 | 설명 |
---|---|
ENTITY | 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 가짐. 주문, 회원 같이 도메인의 고유한 개념을 표현함. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공함. |
VALUE | 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나의 값을 표현할 때 사용됨. 예를들어 주소, 금액 같은 타입. |
AGGREGATE | 애그리거트는 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것. 예를 들어 Order 엔티티, OrderLine 밸류 객체를 ‘주문’ 애그리거트로 묶을 수 잇다. |
REPOSITORY | 도메인 모델의 영속성을 처리한다. 예를 들어 DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공. |
DOMAIN SERVICE | 특정 엔티티에 속하지 않은 도메인 로직을 제공. 예를 들어 할인 금액 계산은 상품, 쿠폰, 구매 금액 등 다양한 조건을 이용해서 구현하는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현함. |
모듈 구성
- 아키텍처의 각 영역은 별도 패키지에 위치한다. 패키지 구성 규칙에 정답이 존재하는 것은 아니지만 영역별로 모듈이 위치할 패키지를 구성할 수 있다.
- 도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성한다.
Reference
- 도메인 주도 개발 시작하기, 최범균 지음
Comments