본문 바로가기
DDD

Ubiquitous Language 적용 전 후 (2)

by kooangelo 2022. 10. 10.

병원비 영수증을 보면 '급여', '비급여' 항목으로 표시된다언뜻봐서는 이해가 잘 되지 않는다그런데 그 표시를 '본인부담금', '공단부담금' 으로 하면 훨씬 이해하기 쉽다.

 

'급여(給與) : 돈이나 물품 따위를 줌. 또는 그 돈이나 물품' 라는 뜻과 같이 소비자가 아닌 제공자, 즉 공단입장에서 구분하는 용어로 표기했기 때문이다물론 급여항목중에서도 전액본인부담금 등으로 세부적인 구분이 나눠지긴 하지만 환자 입장에서, 돈을 내야하는 소비자 입장에서 그러한 구분이 무슨 의미가 있겠는가국민건강보험공단과 병원이 알아서 할 문제이지 소비자는 별 관심없는 어렵고 헤깔리는 용어를 영수증에 표시해서 무엇하겠는가?

 

프로그램 설계와 개발도 마찬가지다개발자가 작성하는 코드도 Provider 입장에서만 생각하기 보다 Consumer/Client 가 보다 이해하기 쉽고, 향후 추가/변경, 확장에 유리할 수 있는 방법이 무엇일지 생각해보자는 것이다.

 

 

개발 프로젝트에서 분석단계 이후 프로그램 목록을 작성한다. 설계/개발/단위테스트 해야할 전체 물량을 나열하고 누가, 언제할 지 정의/계획하는 것이다. 이 때 시간을 무작정 나열하지 않고 몇 단계의 시간 그룹으로 나눈다. 예를 들어 총 3단계 나눠 설계/개발하는데 1단계는 2개월, 2단계는 3개월, 3단계를 다시 2개월. 프로그램을 설계 개발할 때 구체적인 날짜를 넣기 전에 어떤 시간 그룹에 넣을지 정한다. 우선순위, 중요도 등에 따라 의사결정한다. 그러한 시간 그룹을 Sprint(또는 Timebox, Iteration) 라고 부른다. 아래 보여지는 코드 예는 Backlog(프로그램 하나하나, 일감)SprintCommit(할당)하는 예이다.

 

//첫번째 모델
public class BaklogItem extends Entity {
  private SprintId sprintId;
  private BacklogItemStatusType status;
  …

  public void setSprintId(SpringId sprintId) {
    this.sprintId = springId;
  }

  public void setStatus(BacklogItemStatusType status) {
      this.status = status;
  }
}

 

 

//두번째 모델
public class BaklogItem extends Entity {
  private SprintId sprintId;
  private BacklogItemStatusType status;
  …

  public void commitTo(Spring aSprint) {
  
     if(!this.isScheduledForRelease()) {
        throw new IllegalStateException (…); 
     }  

    if(this.isCommittedToSprint()){
     if(!aSprint.sprintId().equals(this.sprintId())) { 
           this.uncommitFromSprint();
     } 
    }

    this.elevateStatusWith(BacklogItemStatus.COMMITTED);
    this.setSpringId(aSprint.sprintId());
  
    DomainEventPublisher.instance()
       .publish(new BacklogItemCommitted(
           this.tanat(),
           this.backlogItemId(),
           this.sprintId()
        ));
  }
  
}

 

 

첫번째 모델과 두번째 모델 각각의 Client Code를 생각해 보면 다음과 같을 것이다.

 

//첫번째 모델의 Client 입장에서 Code

backlogItem.setSpringId(stpringId);

backlogItem.setStatus(BacklogIte,StatusType.COMMITTED);

 

//번째 모델의 Client 입장에서 Code

backlogItem.commitTo(sprint);

 

첫번째 예제는 매우 데이터 중심적인 접근법을 사용한다. 책임은 전적으로 Client 올바르게 스프린트로 백로그 항목을 커밋하는 방법을 알고 있는지에 달려 있다. 이 모델은 도메인 모델이 아니고, 전혀 도움이 되지 않는다. 만약 Client가 실수로 sprintId만 바꾸고 status는 바꾸지 않았거나, 혹은 그 반대의 경우라면 어떻게 될까? 아니면 미래에 새로운 속성을 설정해야 한다면 어떻게 할까? ClientBackogItem 속성 등을 분석해 봐야 정확한 데이터로 매핑할 수 있다. 또한 이 접근법은 BacklogItem 객체의 형태를 노출시키고, 행동이 아닌 데이터 속성에 집중한다. 만약 당신이 setSprintId()setStatus()가 행동이라고 주장한다 해도, 이 행동은 실제적인 비즈니스 도메인의 가치가 하나도 없다는 데 문제가 있다. 이 행동은 도메인 소프트웨어가 모델링해야하는 스프린트로 백로그 항목을 커밋하는 시라니로의 의도를 명히적으로 알려주지 않는다. Client 개발자가 마음속에서 백로그항목을 스프린트로 커밋할 때 필요한 BaclkogItem의 속성을 선택하려는 순간에 인지 과부하를 일으킨다. 이는 데이터 중심적이기 때문에 너무 많은 선택 가능한 옵션을 만들어낸다.

 

 

두번째 예제는 데이터 속성을 노출하는 대신 백로그항목을 스프린트로 커밋하겠다는 점을 명시적이고 정확하게 나타나는 행동을 노출시킨다. 이 도메인 전문가는 다음과 같은 모델의 요구사항에 관해 논의한다.

백로그 항목이 스프린트로 커밋되게 하라. 이는 릴리스를위해 계획이 잡혔을 때만 커밋될 것이다. 만약 이 항목이 이미 다른 스프린트로 커밋됐다면, 먼저 언커밋해야한다. 커밋이 완료되면 이해 당사자에게 알려라

따라서 두번째 예제의 메소드는 컨텍스트 내의 UL를 포착하며, 이때의 Context는 격리된 BacklogItem 타입의 Bounded Context. 그리고 우리는 이 시나리오를 분석함에 따라 첫번째 해결책이 불완전하고 버그를 갖고 있다는 점을 발견하게 된다. 그리고 구현된 Client는 간단하든지 복잡하든지 간에 커밋을 수행하기 위해 무엇이 필요한지 알 필요가 없다.

 

 

분명히 첫번째 예제 보단 두번째 예제에서 BacklogItem을 만들 때 더 많은 고민이 필요하다. 그러나 이런 고민의 과정에서 얻어지는 이득에 비하면 고민에 드는 노력은 그리 크지 않다. 이런 식으로 설계를 배워나간다면 점차 더욱 수월해진다. 결국에는 분명 더 많은 생각, 노력, 협업, 팀 노력의 조직화가 필요하지만, 그렇다고 DDD가 무겁게 느껴질 정도는 아니다. 새로운 생각은 충분히 노력을 들일 만한 가치가 있다.

 

 - Implementing Domain-Driven Design 도메인 주도 설계 구현, 반 버논
   1장 DDD를 시작하며 p.84 ~ p.88 의 내용 발췌 + 첨언(Italic체)

'DDD' 카테고리의 다른 글

전략설계와 전술설계  (0) 2024.07.31
DDD 그리고 MSA  (0) 2022.11.24
Ubiquitous Language 적용 전 후  (0) 2022.09.23
DDD 적용 전후  (1) 2022.09.23
MSA공정과 DDD  (0) 2022.09.05