[도메인 주도 개발 시작하기] 1장: 도메인 주도 개발 시작하기

Cherry·2024년 3월 11일
0

1.1장 도메인이란?

도메인이란 소프트웨어로 해결하고자 하는 문제 영역이다

  • 도메인은 여러 하위 도메인으로 구성된다.
  • 특정 도메인을 위한 소프트웨어라고 해서 도메인이 제공해야 할 모든 기능을 직접 구현하는 것은 아니다.
    - 아래 사진처럼 외부업체의 시스템을 사용할 수 있다.
  • 도메인마다 고정된 하위 도메인이 존재하는것이 아니며 상황에 따라 달라진다.

1.2장 도메인 전문가와 개발자 간 지식 공유

  • 개발자는 요구사항을 분석하고 설계하여 코드를 작성하며 테스트하고 배포한다 (요구사항을 올바르게 이해하는 것이 중요)
  • 요구상항을 올바르게 이해하기 위한 가장 쉬운 방법은 전문가와 직접 대화하는 것이다
  • 이해관계자와 개발자도 도메인 지식을 갖춰야 한다
  • Garbage in Garbage out : 잘못된 값이 들어가면 잘못된 결과가 나온다.

1.3장 도메인 모델

ex)

  • 도메인 모델은 특정 도메인을 개념적으로 표현한 것이다
  • 도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는 데 도움이 된다
  • 도메인을 이해하려면 도메인이 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 해서기능과 데이터를 함께 보여주는 객체 모델은 도메인을 모델링하기에 적합하다

  • 위 사진처럼 도메인 모델이 아닌 상태 다이어그램을 이용해서 주문의 상태 전이를 모델링할 수 있다
  • 도메인 모델은 기본적으로 도메인 자체를 이해하기 위한 개념 모델이므로 코드를 작성하기 위해선 구현 기술에 맞는 구현 모델이 별도로 필요하다
  • 개념 모델과 구현 모델은 서로 다른 것이지만 구현 모델이 개념 모델을 최대한 따르도록 할 수 있다
    ex)
    객체 기반 모델 : 객체 지향 언어를 이용한 구현 모델
    수학적인 모델 : 함수를 이용한 구현 모델

1.4장 도메인 모델 패턴


  • 일반적으로 애플리케이션의 아키텍처는 [그림 1.5]와 같이 네 개의 영역으로 구성되며, 각 영역의 역할은 [표 1.1]과 같다
  • 앞서 살펴본 도메인 모델이 도메인 자체를 이해하는 데 필요한 개념 모델을 의미한다면, 지금 살펴볼 도메인 모델은 마틴 파일러가 쓴 [엔터프라이즈 애플리케이션 아키텍처 패턴] 책의 도메인 모델 패턴을 의미한다
  • 도메인 모델 패턴은 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴이다
  • 도메인 계층의 도메인의 핵심 규칙을 구현한다
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에는 주문 대기 중이거나 상품 준비 중에는 배송지를 변경할 수 있다는 도메인 규칙을 구현하고 있다
  • 큰 틀에서 보면 OrderState는 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 boolean isShippingChangeable() {
        return state == OrderState.PAYMENT_WAITING || state == OrderState.PREPARING;
    }
    ..
}


public enum OrderState {
    PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED;
}
  • 배송지 정보 변경 가능 여부를 Order에서 판단하도록 수정했다
  • 배송지 변경이 가능한지를 판단할 규칙이 주문 상태와 다른 정보를 함께 사용한다면 OrderState만으로는 배송지 변경 가능 여부를 판단할 수 없으므로 Order에서 로직을 구현해야 한다
  • 중요한 점은 주문과 관련된 중요 업무 규칙을 주문 도메인 모델인 Order나 OrderState에서 구현한다는 점이다
  • 핵심 규칙을 구현한 코드는 도메인 모델에만 위치하므로 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할 수 있게 된다

1.5장 도메인 모델 표출

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

주문 도메인과 관련된 몇가지 요구사항

위 요구사항을 통해 주문은 '출고 상태로 변경하기', '배송지 정보 변경하기', '주문 취소하기', '결제 완료하기' 기능을 제공한다는 사실을 도출할 수 있고, Order에 관련 기능을 메서드로 추가할 수 있다.

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

1.6장 엔티티와 밸류

1.6.1 엔티티

  • 엔티티의 가장 큰 특징은 식별자를 가진다
  • 식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 가진다
    엔티티의 식별자는 바뀌지 않는다
  • 엔티티의 식별자는 바뀌지 않고 고유하므로 두 엔티티의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다
  • 이는 코드 상에서 equals() 메서드와 hashCode() 메서드로 구현될 수 있다

1.6.2 엔티티의 식별자 생성

엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라지는데, 보통 다음 중 한 가지 방식으로 생성한다

  • 특정 규칙에 따라 생성
  • UUID나 Nano ID와 같은 고유 식별자 생성기 사용
  • 값을 직접 입력
  • 일련번호 사용(시퀀스나 DB의 자동 증가 칼럼 사용)

1.6.3 밸류 타입

  • 위 그림에서 '받는 사람'과 '주소'라는 개념을 표현하기 위해 각 필드들이 사용된다
  • 밸류 타입은 이렇듯 개념적으로 완전한 하나를 표현할 때 사용한다
  • 밸류 타입을 이용하여 받는 사람(Receiver)과 주소(Address)라는 개념을 표현할 수 있다

1.6.5 도메인 모델에 set 메서드 넣지 않기

  • setter는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다
  • 예를 들어 changeShippinngInfo()가 배송지 정보를 새로 변경하는 의미를 가졌다면 setShippingInfo() 메서드는 단순히 배송지 값을 설정한다는 것은 의미한다

    이렇게 setter를 쓰면 단순히 상태 값만 변경할 것인지 혹은 다른 처리를 위한 코드를 함께 구현할 것인지 애매해진다

  • set 메서드의 또 다른 문제는 도메인 객체가 생성될 때 온전하지 않은 상태가 될 수 있다는 것이다
  • 생성자를 통해 필요한 데이터를 모두 받고, 필요한 경우 생성자 내부에 검증 코드를 추가한다
  • 불변으로 밸류 타입으로 구현하면 자연스럽게 setter를 구현하지 않을 수 있다
  • setter를 구현해야 할 특별한 이유가 없다면 불변 타입의 장점을 살릴 수 있도록 밸류 타입은 불변으로 구현한다

1.7장 도메인 용어와 유비쿼터스 언어

  • 도메인에서 사용하는 용어를 코드에 반영하지 않으면 개발자는 코드의 의미를 해석해야 하는 부담을 지우게 된다

ex) '결제 대기 중', '상품 준비 중', '출고 완료됨', '배송 중', '배송 완료됨', '주문 취소됨'이라는 주문 상태가 있는데 이를 코드 상에서 STEP1, STEP2, STEP3, STEP4, STEP5, STEP6이라는 Enum 상수로 표현되어있을 수 있다
이렇게 구현이 이루어져 있다면 개발자는 코드↔도메인 용어 해석 과정에서 어려움을 겪게 될 것이다
도메인 용어를 사용해서 PAYMENT_WATING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED라는 Enum 상수로 표현한다면 더욱 읽기 쉬운 코드가 될 것이다

에릭 에반스는 도메인 주도 설계에서 언어의 중요함을 강조하기 위해 유비쿼터스 언어(ubiquitous language)라는 용어를 사용했다

전문가, 관계자, 개발자가 도메인과 관련된 공통의 언어를 만들고 이를 대화, 문서, 도메인 모델, 코드, 테스트 등 모든 곳에서 같은 용어를 사용한다
시간이 지나 도메인에 대한 이해가 높아지면 새롭게 이해된 내용을 잘 표현할 수 있는 용어를 찾아내고 이를 다시 공통의 언어로 만들어 함께 사용한다
새로 발견한 용어는 코드나 문서에도 반영해서 산출물에 최신 모델을 적용한다

0개의 댓글