Home > DDD(Domain-Driven Design) > 애그리거트

애그리거트
java
  • 객체 기반 도메인 모델의 구성요소는 다음과 같다.
    • 엔티티
    • 밸류
    • 애그리거트
    • 리포지토리 -> 구현을 위한 도메인 모델
    • 도메인 서비스

애그리거트(Aggregate) - 연관된 엔티티와 밸류를 개념적으로 하나로 묶은 것

  • 애그리거트는 관련된 객체를 하나의 군으로 묶어 주며, 개념상 완전한 한 개의 도메인 모델을 표현한다. 애그리거트를 사용하면 모델 간의 관계를 상위 수준과 개별 모델 수준에서 모두 파악할 수 있다.
    • 위와 같이 개별 객체 수준에서만 모델을 바라보면 상위 수준에서의 관계를 파악하기 어렵다. 이로 인해 전반적인 구조에서 도메인 간의 관계를 파악하기 어려워지고, 이는 코드를 변경하고 확장하는 것을 어렵게 만든다.
  • 애그리거트의 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구사항이다. 도메인 규칙에 따라 함께 생성되는 구성요소는 한 애그리거트에 속할 가능성이 높다. 그러나 “A가 B를 갖는다”라는 요구사항이 있을 때, 반드시 A와 B가 한 애그리거트에 속한다는 것을 의미하는 것은 아니다.
    • “상품 상세 페이지에 들어가면 리뷰 내용도 함께 보여줘야 한다.”라는 요구사항이 있을 때, Product 엔티티와 Review 엔티티는 함께 생성되지 않고 함께 변경되지도 않는다.
  • 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다. 도메인 규칙에 따라 일부 객체를 같은 시점에 생성할 필요가 없는 경우도 있지만 애그리거트에 속한 구성요소는 대부분 함께 생성하고 함께 제거한다.
    • 주문 애그리거트를 만들려면 Order(엔티티), ShippingInfo(밸류), Orderer(밸류)와 같은 관련 객체를 함께 생성해야 한다. Order는 생성했는데 ShippingInfo는 생성하지 않거나, ShippingInfo를 생성했는데 Orderer를 생성하지 않는 경우는 없다.
  • 각 애그리거트는 독립된 객체 군이며 다른 애그리거트를 관리하지 않는다.
    • 주문 애그리거트에서 회원의 비밀번호를 변경하지는 않는다.

애그리거트 루트

  • 애그리거트 루트는 한 애그리거트 전체를 관리하는 책임을 가진 엔티티이다. 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야 하는데, 이 책임을 지는 것이 애그리거트 루트이다. 애그리거트에 속한 객체는 애그리거트 루트에 직·간접적으로 속하게 된다.
    • 주문 애그리거트의 애그리거트 루트는 Order이며, ShippingInfo, Orderer 등 주문 애그리거트에 속한 객체는 Order에 직·간접적으로 속한다.
// 엔티티, 애그리거트 루트
public class Order {
    // 밸류 타입, 불변 객체
    private ShippingInfo shippingInfo;

    // 애그리거트 루트는 도메인 규칙을 구현한 기능을 제공한다.
    public void changeShippingInfo(ShippingInfo newShippingInfo) {
        verifyNotYetShipped();
        setShippingInfo(newShippingInfo);
    }

    private void verifyNotYetShipped() {
        if (state != OrderState.PAYMENT_WAITING && state != OrderState.PREPARING) {
            throw new IllegalStateException("Already shipped");
        }
    }

    // 애그리거트 루트를 통해서만 애그리거트에 속한 객체를 변경해야 한다.
    // set 메서드의 접근 허용 범위는 private이다.
    private void setShippingInfo(ShippingInfo newShippingInfo) {
        // 밸류 타입인 ShippingInfo는 불변이므로 새로운 객체를 할당해서 값을 변경해야 한다.
        // this.shippingInfo.setAddress(newShippingInfo.getAddress())와 같은 코드를 사용할 수 없다.
        this.shippingInfo = newShippingInfo;
    }
}
  • 애그리거트 루트에서 도메인 규칙을 구현한 기능을 제공한다. 애그리거트 루트가 제공하는 메서드는 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다. (객체의 일관성이란 객체의 속성이 불완전하거나 모순되지 않도록 유지하는 원칙이다.)
    • “배송이 시작되기 전까지만 배송지 정보를 변경할 수 있다”는 도메인 규칙이 있다면, 애그리거트 루트인 Order가 이 기능을 구현한 메서드(changeShippingInfo())를 제공한다. 이 때, 해당 메서드는 이 규칙에 따라 배송 시작 여부를 확인하고 규칙을 충족할 때만 배송지 정보를 변경해야 한다.
  • 애그리거트에 속한 객체의 일관성을 위해, 애그리거트 루트를 통해서만 애그리거트에 속한 객체를 변경해야 한다. 이를 위해, 엔티티에서 공개 set 메서드를 가급적 피하고 밸류 타입은 불변으로 구현하는 것을 습관적으로 적용해야 한다.
    • 애그리거트 루트인 Order에서 ShippingInfo를 가져와 직접 정보를 변경하면 안된다.

  • 참고 자료
    도메인 주도 개발 시작하기(저자: 최범균)
    https://velog.io/@andy230/Aggregate-%EC%95%A0%EA%B7%B8%EB%A6%AC%EA%B1%B0%ED%8A%B8