Today I Learned

[도메인 주도 설계] 07. 언어의 사용(확장 예제) (2) 본문

도메인 주도 설계

[도메인 주도 설계] 07. 언어의 사용(확장 예제) (2)

하이라이터 2022. 1. 6. 02:18
728x90

시나리오 연습

  • 예제 애플리케이션 기능 : 화물의 목적지 변경
    • Customer로부터 화물의 목적지에 대한 변경 요청이 올 수 있다. Delivery Specification은 VALUE OBJECT이므로 기존 것을 버리고 새로운 Delivery Specification을 획득해 대체하면 간단하게 처리할 수 있다.
  • 예제 애플리케이션 기능 : 반복 업무
    • 같은 고객이 반복적으로 예약하는 내용이 비슷해서 기존 Cargo를 새로운 Cargo의 프로토타입으로 사용하고 싶다.
    • Cargo는 ENTITY이며 AGGREGATE의 루트이므로 신중하게 복사해야 한다. AGGREGATE의 외부에는 영향을 주지 않아야 한다.
      • Delivery History : 기존 이력은 사용하지 않으므로 빈 상태로 새로 만든다.
      • Customer Role : 동일한 역할을 수행할 가능성이 높으므로 Customer에 대한 참조를 키로 갖는 Map으로 복사한다. Customer 객체 자체는 복사하지 않도록 주의해야 한다.
      • Tracking ID : 새로운 Cargo를 생성할 때는 동일한 출처에서 만들어진 새 Tracking ID를 제공해야 한다.

객체 생성

  • Cargo에 대한 FACTORY와 생성자
    • 불변식을 이행하거나, 자신의 식별성을 그대로 갖는 객체를 생성하기 위해 생성자가 필요하다.
      public Cargo newCargo(Cargo Prototype)​
    • FACTORY에서 반환되는 결과는 빈 Delivery History와 Delivery Specification이 담긴 Cargo일 것이다.
      public Cargo(String id) {
        trackingID = id;
        deliveryHistory = new DeliveryHistory(this);
        customerRoles = new HashMap();
      }
    • Delivery History의 생성자는 Cargo에 의해 배타적으로 사용되므로 Cargo의 구성은 캡슐화된다.
  • Handling Event 추가
    • 모델의 Handling Event는 적재와 하역에서 포장, 입고, 기타 Carrier에 관계되지 않은 활동에 이르기까지 다양하게 특화되어 있는 Handling Event 클래스를 캡슐화하는 추상적인 개념이다.
    • 각 타입에 대한 기반클래스(Handling Event)에 FACTORY METHOD를 추가하면 인스턴스 생성이 추상화되며, 클라이언트가 구현에 대해 알지 않아도 된다.
    • 하지만 Cargo - Delivery History - Handling Event - Cargo 에 이르는 참조의 순환은 인스턴스 생성을 복합하게 한다.

리팩터링할 시간 : Cargo AGGREGATE의 설계 대안

  • Handling Event를 추가할 때 Delivery History를 갱신해야 하는 탓에 Cargo AGGREGATE가 트랜잭션에 참여하게 된다. 경합을 겪지 않고도 Handling Event에 진입할 수 있도록 또 다른 설계를 고려해봐야 한다.
  • Delivery History의 Handling Event 컬렉션을 질의로 교체하면 자체적인 AGGREGATE 외부에 아무런 무결성 문제를 일으키지 않고 추가될 수 있다.
  • Handling Event에 대한 REPOSITORY를 추가하여 특정 Cargo에 관계된 Event에 대한 질의를 지원할 수 있다.

해운 모델의 MODULE

  • 이제 해운 모델에서 좀 더 규모가 큰 부분을 살펴보면서 모델을 MODULE로 조직화하는 것이 모델에 어떠한 영향을 주는지 살펴보자.
  • 다음은 인프라스트럭처 주도 패키지화를 적용한 모델이다. 객체는 제각기 자신이 따르는 패턴에 따라 무리를 이룬다. 그 결과 낮은 응집도와 높은 결합도를 가진 모듈이 만들어졌다.
  • 프로젝트에는 응집력 있는 개념을 찾고 다른 사람들과 의사소통하고자 하는 바에 집중해야한다.
  • 각 MODULE의 이름은 팀 언어에 기여한다. 회사에서는 고객을 위해 해운업무를 수행하므로 고객에게 결제를 요구할 수 있다.

새로운 기능 도입 : 할당량 검사

    • 이제 첫번째 주요 새 기능을 추가해보자. 회사의 영업부서에서는 별도의 소프트웨어를 이용해 클라이언트의 관계와 영업 예측 등과 같은 사항을 관리한다. 이를 통해 예약 미달이나 초과예약을 방지한다.
    • 이 기능이 예약 시스템에 통합되어서, 예약이 들어오면 할당 내역을 검사해서 해당 예약을 수락할지 여부를 확인하고자 한다.

  • 두 시스템의 연계
    • 영업 관리 시스템은 여기서 사용한 것과 동일한 모델을 염두에 두고 만들어지지는 않았다.
    • Booking Application에서 직접적으로 영업관리 시스템과 상호작용하기 보다는 각 모델의 언어를 서로 번역하는 역할을 지닌 또다른 클래스를 만들어보자.
    • 그 클래스는 현재 개발중인 애플리케이션에 필요한 기능만을 노출할 것이며, 도메인 모델의 관점에서 그와 같은 기능을 재추상화할 것이다.
    • Allocation Checker(할당 검사기)라는 클래스를 이용해 SERVICE를 구현하자. 이렇게하면 다른 통합이 필요할 경우 그 책임을 이행하는 SERVICE로 또 다른 번역기가 만들어 질 수 있다.
  • 모델 강화 : 업무 분야 나누기
    • 어떤 유형의 Cargo가 있는지 정의하기 위해 Enterprise Segment(업무분야)라는 클래스를 부가적인 VALUE OBJECT로 생성한다.
    • Allocation Checker는 Enterprise Segment와 외부 시스템에서 사용되는 분류명 간의 번역을 수행한다.
    • Cargo Repository는 Enterprise Segment를 기반으로 질의를 제공한다.
    • 하지만 이 설계에도 몇 가지 문제가 있다.
      1. Booking Application에 초과예약을 체크하는 규칙을 적용하는 책임을 부과했다. 업무 규칙을 이행하는 것은 도메인의 책임이며 응용계층에서 수행해서는 안된다.
      2. Booking Application에서 어떻게 Enterprise Segment를 도출하는지가 분명하지 않다.
    • 이러한 책임은 모두 Allocation Checker에 속한다. Allocation Checker의 인터페이스를 변경해서 SERIVCE를 분리하고 상호작용을 분명하고 명시적으로 만들 수 있다.

최종 검토

설계와 관련된 마지막 질문은 다음과 같다.

"Enterprise Segment를 도출하는 책임을 Cargo에 부여하지 않은 이유는 무엇인가?"

도출로 얻는 데이터가 모두 Cargo에 있다면 이러한 데이터를 Cargo의 유도속성(derived attribute)으로 만드는게 좋아보인다.

하지만 동일한 ENTITY도 각기 다른 목적으로 서로 다르게 분할될 수 있다. 여기서는 예약할당을 목적으로 특정 Cargo를 도출했지만, 세금 회계 목적으로는 전혀 다른 Enterprise Segment가 만들어질 수 있다.

따라서 이러한 값을 도출할 책임은 분할 규칙을 적용할 데이터가 담긴 객체보다는 분할 규칙을 알고 있는 객체에 있는 것이 적절하다.

그 규칙들은 개별 "Strategy(전략)"로 나뉠 수 있으며, 이러한 Strategy 객체는 Cargo에 전달되어 Enterprise Segment를 도출할 수 있게 만들어준다.

 

 

 

 

 

 

728x90
Comments