티스토리 뷰

 

'도메인 주도 개발 시작하기'와 '도메인 주도 설계'를 정리한 글입니다.

 

 

도메인

  • 소프트웨어로 해결하고자 하는 문제 영역
    • ex
      • 카탈로그 : 고객에게 구매할 수 있는 상품 목록 제공
      • 주문 : 고객의 주문 처리
      • 혜택 : 쿠폰이나 특별 할인 같은 서비스 제공
      • 배송 : 고객에게 구매한 상품을 전달하는 일련의 처리 과정
  • 하나의 하위 도메인은 다른 하위 도메인과 연동하여 완전한 기능을 제공한다.
    • ex. 고객이 물건을 구매한다 → 주문, 결제, 배송, 혜택 기능이 엮인다.

도메인 모델

  • 특정 도메인을 개념적으로 표현한 것
    • 도메인 모델은 목적을 위해 현실 세계에 존재하는 것을 가공하고 편집하여 우리에게 정보를 제공한다.
  • 특정 다이어그램이 아니라 다이어그램으로 전달하려는 아이디어이자 목적을 가진 의사소통 수단
  • 이 의사소통 수단은 회의, 기획, 디자인, 개발에 사용되어야 한다.

 

도메인 영역 내 구성요소

 

엔티티(Entity)

  • 고유의 식별자를 갖는 객체
  • 엔티티는 자신의 생명주기를 갖는다.
    • 생명주기 동안 형태와 내용이 바뀔 수도 있지만 연속성은 유지해야 한다.
  • 도메인 모델의 데이터를 포함하며, 해당 데이터와 관련된 기능을 제공한다.

- 한 객체가 속성보다는 식별성으로 구분될 경우 모델 내에서 이를 해당 객체의 주된 정의로 삼아라.
- 클래스 정의를 단순하게 하고 생명주기의 연속성과 식별성에 집중하라.
- 객체의 형태나 이력에 관계없이 각 객체를 구별하는 수단을 정의하라.
- 객체의 속성으로 객체의 일치 여부를 판단하는 요구사항에 주의하라.
- 각 객체에 대해 유일한 결과를 반환하는 연산을 정의하라.
- 모델은 동일하다는 것이 무슨 의미인지 정의해야 한다.

  • DB 모델의 엔티티 ≠ 도메인 모델의 엔티티
    • 차이점
      • 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공한다.
        • 예) 주문 엔티티 : 주문과 관련된 데이터 뿐만 아니라 배송지 주소 변경을 위한 기능을 함께 제공한다.
      • 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 값 객체 타입을 이용해서 표현할 수 있다.
public class Order {
    // 주문 도메인 모델의 데이터
    private OrderNo number;
    private Orderer orderer;
    private ShippingInfo shippingInfo;

    // 도메인 모델 엔티티는 도메인 기능도 함께 제공
    public void changeShippingInfo(ShippingInfo newShippingInfo) {
        checkShippingInfoChangeable();
        setShippingInfo(newShippingInfo);
    }

    private void setShippingInfo(ShippingInfo newShippingInfo) {
        if (newShippingInfo == null) throw new IllegalArgumentException();
        // 밸류 타입의 데이터를 변경할 때는 새로운 객체로 교체한다.
        this.shippingInfo = newShippingInfo;
    }
} 

 

값 객체(Value Object)

  • 고유의 식별자를 갖지 않는 객체
  • 주로 개념적으로 하나의 값을 표현할 때 사용된다.
  • 엔티티의 속성으로 사용할 뿐만 아니라 다른 값 객체 타입의 속성으로도 사용할 수 있다.
public class ShippingInfo {
    private Address address;
    private String message;
    private Receiver receiver;
}

- 모델에 포함된 어떤 요소의 속성에만 관심이 있다면 그것을 VALUE OBJECT로 분류하라.
- VALUE OBJECT에서 해당 VALUE OBJECT가 전하는 속성의 의미를 표현하게 하고 관련 기능을 부여하라.
- 또한 VALUE OBJECT는 불변적(immutable)으로 다뤄라.
- VALUE OBJECT에는 아무런 식별성도 부여하지 말고 ENTITY를 유지하는 데 필요한 설계상의 복잡성을 피하라.

 

애그리거트(Aggregate)

  • 연관된 엔티티와 값 객체를 개념적으로 하나로 묶은 것
  • 일관성을 관리하는 기준이 된다.
  • 애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.
    • 루트 엔티티 : 애그리거트에 속해 있는 엔티티와 값 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다.
    • 애그리거트를 사용하는 코드는 애그리거트 루트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 값 객체에 접근한다. → 애그리거트의 내부 구현을 숨겨서 애그리거트 단위로 구현을 캡슐화할 수 있도록 돕는다.
  • 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.

- ENTITY와 VALUE OBJECT를 AGGREGATE로 모으고 각각에 대해 경계를 정의하라.
- 한 ENTITY를 골라 AGGREGATE의 루트로 만들고 AGGREGATE 경계 내부의 객체에 대해서는 루트를 거쳐 접근할 수 있게 하라.
- AGGREGATE 밖의 객체는 루트만 참조할 수 있게 하라.
- 내부 구성요소에 대한 일시적인 참조는 단일 연산에서만 사용할 목적에 한해 외부로 전달될 수 있다.
- 루트를 경유하지 않고는 AGGREGATE의 내부를 변경할 수 없다.
- 이런 식으로 AGGREGATE의 각 요소를 배치하면 AGGREGATE 안의 객체와 전체로서의 AGGREGATE의 상태를 변경할 때 모든 불변식을 효과적으로 이행할 수 있다.

 

도메인 서비스(Service)

  • 특정 엔티티에 속하지 않은 도메인 로직을 제공
    • 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.
  • ex. 계산 로직 : 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직
    • 할인 금액 계산 → 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 이용해 구현한다.
  • ex. 외부 시스템 연동이 필요한 도메인 로직 : 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직

- 도메인의 중대한 프로세스나 변환 과정이 ENTITY와 VALUE OBJECT의 고유한 책임이 아니라면 연산을 SERVICE로 선언되는 독립 인터페이스로 모델에 추가하라.
- 모델의 언어라는 측면에서 인터페이스를 정의하고 연산의 이름을 UBIQUITOUS LANGUAGE의 일부가 되게끔 구성하라.
- SERVICE는 상태를 갖지 않게 만들어라.

 

리포지터리(Repository)

  • 도메인 모델의 영속성을 처리한다.
  • 리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
public interface OrderRepository {
    Order findByNumber(OrderNumber number);
    void save(Order order);
    void delete(Order order);
}

 

 

참고자료

댓글