Today I Learned

[도메인 주도 설계] 09. 암시적인 개념을 명확하게 (1) 본문

도메인 주도 설계

[도메인 주도 설계] 09. 암시적인 개념을 명확하게 (1)

하이라이터 2022. 1. 20. 20:56
728x90

도메인의 본질적인 개념을 모댈 내에 표현하고, 지식탐구와 리팩토링을 반복하면서 심층적인 모델로 정제하게 된다.

 

언어에 귀 기울여라

  • 도메인 전문가가 사용하는 언어에 모델의 개념에 기여하는 실마리가 있다.
  • 팀에서 사용하는 언어와 전문가의 견해, 도메인과 관련된 문서를 통해 암시적인 개념을 드러내는 단서를 찾아야 한다.
  • 사용자나 도메인 전문가가 설계상의 어디에도 표현되어 있지 않은 어휘를 사용하고 있다면 주의해야한다.

  • 예제 : 해운 모델의 누락된 개념에 귀 기울이기
    • 기존의 예약 애플리케이션에서는 화물에 대한 운송 계획을 수립할 때 Rounting engine을 사용해서 각 운항 구간(leg)을 데이터베이스 테이블의 한 레코드로 저장한다.
    • 그런데 도메인 전문가와 대화를 통해 화물에 대한 운항일정(Itinerary)가 중요하다는 것을 알게 되었다.
    • 이렇게하면 라우팅 엔진이 데이터베이스 테이블 스키마를 알 피요가 없다는 장점도 있었다.
    • 그래서 데이터를 테이블에 저장하는 대신 Routing Service가 Itinerary 객체를 반환하도록 만들었다.
    • 명시적인 Itinerary 객체로 리팩터링한 결과로 얻게된 이점은 다음과 같다.
      1. Routing Service의 인터페이스를 좀 더 표현력 있게 정의
      2. Routing Service에서 예약 데이터베이스 테이블로의 결합 제거
      3. 예약 애플리케이션과 운영 지원 애플리케이션 간의 (Itinerary 객체를 공유하는) 관계를 명확하게 표현
      4. Itinerary로부터 예약 보고서와 운영지원 애플리케이션 모두에 대한 적재/하역 시간 도출이 가능해져서 중복코드 줄어듦
      5. 예약 보고서로부터 도메인 로직을 제거하고 별도의 도메인 계층으로 옮김
      6. UBIQUITOUS LANGUAGE를 확장함으로써 개발자와 도메인 전문가, 그리고 개발자 간의 모델과 설계에 대한 정확한 논의가 가능해짐

어색한 부분을 조사하라

  • 필요한 개념이 늘 대화나 문서로 인식할 수 있을 만틈 확연히 드러나지는 않는다.
  • 설계에서 어색한 부분을 조사하고 도메인 전문가와 함꼐 누락된 개념을 발견해야 한다.

  • 예제 : 이자 수익 예제 - 어려운 방식으로 접근하기
    • 다음은 기업대출과 그 밖의 이자부 자산에 투자하는 금융회사의 애플리케이션이다.
    • 매일 밤 실행되는 배치 스크립트는 해당 일자의 이자를 계산하기 위해 모든 Assert(자산)의 CalculateInterestForDate() 메서드를 호출한다.
    • 메서드의 반환값(수익금)과 특정 원장(ledger)의 이름은 회계 프로그램에 대한 공용 인터페이스를 제공하는 SERVICE로 전달한다.
    • 회계 소프트웨어는 지정된 원장에 수익금을 기입한다. Assert에 대한 일별 수수료 계산도 유사한 방식으로 처리된다.
    • 하지만 점점 복잡해지는 이자 계산 방식으로 리팩토링이 필요해보였다. 전문가와의 대화를 통해 이자수익(interest earned)와 상환(payment)이 별개의 기입 항목이며, 원장도 각각 기입한다는 것을 알게되었다.
    • 이를 바탕으로 리팩터링된 애플리케이션에서 야간 배치 스크립트는 각 Assert(자산)의 calculateAccrualsThroughDate() 메서드를 실행한다.
    • 이 메서드는 Accural(발생)의 컬렉션을 반환하며, 각 Accural의 금액은 명시된 원장에 기입된다.
    • 새로운 모델은 다음과 같은 장점이 있다.
      1. "발생"이라는 용어를 추가해서 UBIQUITOUS LANGUAGE가 풍부해졌다.
      2. 상환에서 발생을 분리했다.
      3. 도메인 지식(이를테면, 어느 원장에 기입할 것인지와 같은)을 스크립트에서 도메인 계층으로 옮겼다.
      4. 업무에 적합하게 수수료와 이자를 하나의 개념으로 묶어 코드상의 중복을 제거했다.
      5. 새로운 수수료와 이자 처리 방식을 간단하게 추가하기 위한 Accural Schedule(발생 기록)

다소 불명확한 개념을 모델링하는 법

  • "명사와 동사"로 표현되지 않는 다른 중요한 범주의 개념도 모델 내에 명시적으로 표현할 수 있다.

  • 명시적인 제약조건
    • "Bucket" 객체는 제한된 용량을 초과해서 저장할 수 없다는 불변식을 만족시켜야 한다.

    • 불변식이 간단한 경우에는 다음처럼 contents를 변경하는 개별 연산 안에 조건 로직을 사용해서 불변식을 보장할 수 있다.
      class Bucket {
        private float capacity;
        private float contents;
        
        public void pourIn(float addedVolume) {
          if (contents + addedVolume > capacity) {
            contents = capacity;
          } else {
            contents = contents + addedVolume;
          }
        }
      }
    • 하지만 제약조건을 파악하기 어려운 복잡한 클래스라면 제약조건을 따로 분리할 수 있다.
      class Bucket {
        private float capacity;
        private float contents;
        
        public void pourIn(float addedVolume) {
          float volumePresent = contents + addedVolume;
          contents = constrainedToCapacity(volumePresent);
        }
        
        private float constrainedToCapacity(float volumePlacedIn) {
          if (volumePlacedIn > capacity) return capacity;
          return volumePlaceIn;
        }
      }

  • 어떤 제약조건을 포함한 객체의 설계가 잘못되어있다는 조짐
    1. 제약조건을 평가하려면 해당 객체의 정의에 적합하지 않은 데이터가 필요하다.
    2. 관련된 규칙이 여러 객체에 걸쳐 나타나며, 동일한 계층구조에 속하지 않는 객체 간에 중복 또는 상속 관계를 강요한다.
    3. 설계와 요구사항에 관한 다양한 논의는 제약조건에 초점을 맞춰 이뤄지지만 정작 구현 단계에서는 절차적인 코드에 묻혀 명시적으로 표현되지 않는다.
  • 제약조건이 객체가 담당하는 기본 책임을 모호하게 만들거나 모델 내에 명확하게 표현되어 있지 않다면 제약조건을 명시적인 객체로 분리하거나, 나아가 일련의 객체와 관계의 집합으로 모델링할 수 있다.
728x90
Comments