Domain Model

|

DDD START! 도메인 주도 설계 구현과 핵심 개념 익히기

이 포스팅은 아래 책을 보고 공부한 내용을 요약한 글입니다.

image


도메인

도메인(Domain)은 소프트웨어로 해결하고자 하는 문제 영역입니다. 하나의 도메인은 여러 개의 하위 도메인으로 나눌 수 있습니다.

예를 들면, 온라인 서점 도메인은 아래 그림과 같이 여러 하위 도메인으로 나눠집니다.

image

특정 도메인용 소프트웨어라고 해서 모든 기능을 구현하는 것은 아닙니다. 아래 그림과 같이 외부 시스템과 연동해서 사용하기도 합니다.

image


도메인 모델

도메인 모델은 특정 도메인을 개념적으로 표현한 것입니다. 아래는 주문 모델을 객체 모델로 표현한 그림입니다.

image

도메인 모델이 도메인의 모든 내용을 담고 있지는 않지만, 위 도메인 모델을 보면 주문(Order)은 주문 번호와 총 금액을 갖고 있고, 배송 정보를 변경할 수 있음을 알 수 있습니다. 또한 취소(Cancel) 할 수 있는 것도 알 수 있습니다.

이렇게 도메인 모델을 이용해서 여러 관계자들이 도메인을 이해하고 지식을 공유하는데 도움을 줄 수 있습니다.

도메인 모델은 객체 외에도 아래 그림처럼 상태 다이어그램을 이용해서 표현할 수도 있습니다.

image

도메인 모델은 꼭 UML 표기법만 사용할 필요는 없습니다. 관계가 중요하다면 그래프로, 계산 규칙이 중요하다면 수학 공식으로 도메인 모델을 만들 수도 있습니다. 표현 방식이 중요하지는 않습니다.

도메인 모델은 개념 모델입니다. 개념 모델을 이용해서 바로 코드를 작성할 수 있는 것은 아니기 때문에 구현 모델은 따로 필요합니다. 개념 모델과 구현 모델은 서로 다르지만 최대한 서로 따르게 할 수는 있습니다.

도메인에 따라 용어가 바뀔 수 있기 때문에 여러 하위 도메인을 하나의 다이어그램에 모델링하는 것은 좋지 않습니다. 예를 들어, ‘상품’이라는 용어는 카탈로그에서의 상품과 배송에서의 상품이 서로 다릅니다. 즉, 이 경우 카탈로그 도메인 모델과 배송 도메인 모델을 따로 만들어야 한다는 뜻입니다.


개념 모델과 구현 모델

개념 모델은 순수하게 문제를 분석한 결과물입니다. 개념 모델은 데이터베이스, 트랜잭션 처리, 성능 등을 고려하지 않기 떄문에 실제 코드에 개념 모델을 그대로 사용할 수는 없습니다.

개념 모델을 처음부터 완벽한 모델로 만드는 것은 아주 어렵습니다. 소프트웨어를 개발하면서 개발자와 관계자들이 해당 도메인을 더 잘 이해할 수 있습니다. 프로젝트 초기에 완벽하 도메인 모델을 만들더라도 결국 모델을 수정하거나 보완하는 경우가 발생하게 됩니다.

따라서, 처음부터 완벽한 개념 모델을 만들기보다 전반적 개념을 알 수 있는 수준으로 개념 모델을 작성하는 것이 좋습니다.


도메인 모델 도출 방법

도메인을 모델링할 때 기본은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것입니다. 이 과정은 요구사항에서 출발합니다.

주문 도메인과 관련된 요구사항은 다음과 같습니다.

  • 최소 한 종류 이상의 상품을 주문해야 한다.
  • 한 상품을 한 개 이상 주문할 수 있다.
  • 총 주문 금액은 각 상품의 구매 가격 합을 모두 더한 금액이다.
  • 각 상품의 구매 가격 합은 상품 가격에 구매 개수를 곱한 값이다.
  • 주문할 떄 배송지 정보를 반드시 지정해야 한다.
  • 배송지 정보는 받는 사람 이름, 전화번호, 주소로 구성된다.
  • 출고를 하면 배송지 정보를 변경할 수 없다.
  • 출고 전에 주문을 취소할 수 있다.
  • 고객이 결제를 완료하기 전에는 상품을 준비하지 않는다.

이 요구사항에서 알 수 있는 것은 주문(Order)은 아래 기능을 제공한다는 것입니다.

  • 출고 상태로 변경
  • 배송지 정보 변경
  • 주문 취소
  • 결제 완료로 변경

코드로 표현하면 다음과 같습니다.

public class Order {
    public void changeShipped() {...}
    public void changeShippingInfo(ShippingInfo newShipping) {...}
    public void cancel() {...}
    public void completePayment() {...}
}

그 외 위 요구사항 중 아래 요구 사항을 반영하면

  • 한 상품을 한 개 이상 주문할 수 있다.
  • 총 주문 금액은 각 상품의 구매 가격 합을 모두 더한 금액이다.

OrderLine 객체를 구성할 수 있고, Order와의 관계를 표현할 수 있습니다.

또한, 아래 요구 사항을 보면

  • 출고를 하면 배송지 정보를 변경할 수 없다.
  • 출고 전에 주문을 취소할 수 있다.
  • 고객이 결제를 완료하기 전에는 상품을 준비하지 않는다.

주문의 상태(OrderState)가 필요함을 알 수가 있습니다.

public enum OrderState {
    PAYMENT_WATING,
    PREPARING,
    SHIPPED,
    DELIVERING,
    DELIVERY_COMPLETED,
    CANCELED,
}

이와 같은 방식으로 요구사항으로부터 도메인 모델을 점진적으로 만들어 나갑니다.


Entity와 Value

도출한 모델은 크게 EntityValue로 구분할 수 있습니다.

image

Entity

Entity의 가장 큰 특징은 식별자를 갖는다는 것입니다. 식별자는 객체마다의 고유값입니다. 예를 들면 주문 도메인에서 Order는 주문 번호를 가지며, 주문 번호는 Order의 식별자가 됩니다.

배송지가 변경되어도 주문 번호가 바뀌지 않는 것처럼 식별자는 Entity의 생성, 변경, 삭제까지 계속 유지됩니다.


Value

아래 코드에서 receiverNamereceiverPhoneNumber는 서로 다른 데이터를 갖지만, 두 필드는 개념적으로 받는 사람을 의미합니다. 즉, 두 필드가 하나의 개념을 표현하고 있습니다.

public class ShippingInfo {
    private String receiverName;
    private String receiverPhoneNumber;
    // ...
}

Value는 개념적으로 완전한 하나를 표현할 때 사용합니다. 위의 코드에서 받는 사람을 위한 Value 타입인 Receiver를 다음과 같이 작성할 수 있습니다.

public class Receiver {
    private String name;
    private String phoneNumber;
    // ...
}

Value 타입이 꼭 두 개 이상의 데이터를 가질 필요는 없습니다. 의미를 명확하게 표현하기 위해 사용하는 경우도 있습니다. 예를 들면 다음과 같습니다.

public class Money {
    private int value;
    // ...
}