도메인 주도 개발 시작하기

[도메인 주도 개발 시작하기] 도메인 모델 패턴

깊게 생각하고 최선을 다하자 2025. 1. 7. 16:01

 

일반적인 애플리케이션의 아키텍처는 아래와 같이 네 개의 영역으로 구성된다.

 

 

 

- 각 영역의 역할은 아래와 같다. 

영역 설명
사용자 인터페이스 또는 표현 사용자의 요청을 처리하고 사용자에게 정보를 보여준다.
여기서 사용자는 소프트웨어를 사용하는 사람뿐만 아니라
외부 시스템일 수도 있다.
응용 사용자가 요청한 기능을 실행한다.
업무 로직을 직접 구현하지 않으며
도메인 계층을 조합해서 기능을 실행한다.
도메인 시스템이 제공할 도메인 규칙을 구현한다.
인프라스트럭쳐 데이터베이스나 메시징 시스템과 같은 외부 시스템과의 
연동을 처리한다. 

 

- 지금 살펴볼 도메인 모델은 마틴 파울러가 쓴 '엔터프라이즈 애플리케이션 아키텍처 패턴' 책의 도메인 모델 패턴을 의미한다.

  도메인 모델은 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 말한다.

 

- 도메인 계층은 도메인의 핵심 규칙을 구현한다.

  주문 도메인의 경우 '출고 전에 배송지를 변경할 수 있다'라는 규칙과

  '주문 취소는 배송 전에만 할 수 있다'라는 규칙을 구현한 코드가 

  도메인 계층에 위치하게 된다.

  이런 도메인 규칙을 객체 지향 기법으로 구현하는 패턴이 도메인 모델 패턴이다. 

  예를 들어 다음 코드를 보자. 

 

public class Order {
   private OrderState state;
   private ShippingInfo shippingInfo;
   
   public void changeShippingInfo(ShippingInfo newShippingInfo) {
      if(!state.isShippingChangeable()) {
         throw new IllegalStateException("can't change shipping in " + state);
      }
      this.shippingInfo = newShippingInfo;
   }
   ...
 }
 
 public enum OrderState {
    PAYMENT_WAITING {
       public boolean isShippingChangeable(){
          return true;
       }
   },
   PREPARING {
       public boolean isShippingChangeable(){
          return true;
       }
   },
   SHIPPED, DELIVERING, DELIVERY_COMPLETED;
   
   public boolean isShippingChangeable() {
      return false; 
   }
 }

 

- 이 코드는 주문 도메인의 일부 기능을 도메인 모델 패턴으로 구현한 것이다.

  주문 상태를 표현하는 OrderState는 배송지를 변경할 수 있는지를 검사할 수 있는 

  isShippingChangeble() 메서드를 제공하고 있다. 

 

- 코드를 보면 주문 대기 중(PAYMENT_WAITING) 상태와 

  상품 준비중(PREPARING) 상태의 isShippingChangble() 메서드는 true를 리턴한다.

  즉, OrderState는 주문 대기 중이거나 상품 준비 중에는 배송지를 변경할 수 있다는 

  도메인 규칙을 구현하고 있다. 

 

- 실제 배송지 정보를 변경하는 Order 클래스의 changeShippingInfo() 메서드는

  OrderState의 isShippingChangeble() 메서드를 이용해서 변경 가능한 경우에만 

  배송지를 변경한다. 

 

- 큰 틀에서 보면 OrderState는 Order에 속한 데이터이므로 

  배송지 정보 변경 가능 여부를 판단하는 코드를 Order로 이동할 수도 있다.

  다음은 Order 클래스에서 판단하도록 수정한 코드를 보여주고 있다. 

 

public class Order { 
    private OrderState state;
    private ShippingInfo shippingInfo;
    
    public void changeShippingInfo(ShippingInfo newShippingInfo){
         if(!isShippingChangeable()){
             throw new IllegalStateException("can't change shipping in " + state);
         }
         
         this.shippingInfo = newShippingInfo;
    }
    
    private boolan isShippingChangeble(){
        return state == OrderState.PAYMENT_WAITING || state == OrderState.PREPARING;
    }
    ...
 }
 
 public enum OrderState {
    PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED
 }

 

- 배송지 변경이 가능한지를 판단할 규칙이 주문 상태와 다른 정보를 함께 사용한다면 

  OrderState만으로는 배송지 변경 가능 여부를 판단할 수 없으므로

  Order에서 로직을 구현해야 한다. 

 

- 배송지 변경 가능 여부를 판단하는 기능이 Order에 있든 OrderState에 있든 중요한 점은

  주문과 관련된 중요 업무 규칙을 주문 도메인 모델인 Order나 OrderState에서 구현한다는 점이다.

  핵심 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에 

  규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 

  변경 내역을 모델에 반영할 수 있게 된다.