Skip to content

Aggregate and Root

애그리거트(Aggregate)는 관련된 객체들을 하나의 단위로 묶어 데이터 변경의 일관성을 보장하는 경계다.

애그리거트의 구조와 루트 엔티티

Section titled “애그리거트의 구조와 루트 엔티티”

애그리거트는 하나 이상의 엔티티와 밸류 오브젝트(VO)로 구성되며, 이 중 중심이 되는 엔티티를 애그리거트 루트(Aggregate Root)라고 한다.

  • 외부 통제: 외부 객체는 오직 루트를 통해서만 애그리거트 내부 객체에 접근 가능
  • 상태 보호: 루트 엔티티는 애그리거트 내부의 모든 객체가 비즈니스 규칙을 준수하도록 관리
  • 전역 식별자: 루트 엔티티만이 전역적으로 고유한 식별자를 가짐 (내부 엔티티는 애그리거트 내에서만 유효한 지역 식별자 사용)
graph TD
Root[주문 Order - Root] --> Line[주문 항목 OrderLine]
Root --> Shipping[배송지 ShippingAddress - VO]
Root --> Payment[결제 정보 PaymentInfo - VO]
Line --> Product[상품 참조 ProductId - ID]

애그리거트 내의 모든 상태 변경은 비즈니스 규칙을 만족해야 한다.

  • 루트를 통한 변경: 외부에서 OrderLine의 수량을 직접 수정하는 대신 order.changeQuantity()와 같이 루트의 메서드를 호출
  • 원자적 변경: 하나의 애그리거트 내 데이터는 트랜잭션이 끝나는 시점에 모두 함께 올바른 상태여야 함

거대한 애그리거트는 동시성 문제를 유발하고 시스템의 유연성을 떨어뜨린다.

  • 성능 향상: 객체 로딩 속도가 빠르고 메모리 사용량이 적음
  • 충돌 방지: 여러 사용자가 동일한 애그리거트를 동시에 수정할 확률 감소
  • 명확한 책임: 도메인의 핵심 규칙에만 집중할 수 있는 최소한의 범위 설정

애그리거트 간의 결합도를 낮추기 위해 다른 애그리거트를 직접 참조하지 않고 식별자(ID)만 보유한다.

구분객체 참조ID 참조
결합도높음 (함께 로딩·수정 가능성)낮음 (독립적인 라이프사이클)
성능지연 로딩 등 복잡한 설정 필요단순하고 명확한 쿼리 수행
트랜잭션여러 애그리거트가 묶일 위험애그리거트 단위 트랜잭션 강제
// ID 참조를 통한 애그리거트 간의 느슨한 결합
public class Order {
private OrderId id;
private MemberId ordererId; // Member 객체 대신 ID만 참조
private List<OrderLine> orderLines;
}

트랜잭션 범위와 일관성 유지 전략

Section titled “트랜잭션 범위와 일관성 유지 전략”

원칙적으로 하나의 트랜잭션에서는 하나의 애그리거트만 수정한다.

  • 강한 일관성: 하나의 트랜잭션 내에서 즉각적으로 일관성 보장
  • 최종 일관성: 다른 애그리거트의 상태 변경이 필요한 경우 도메인 이벤트를 발행하여 비동기적으로 처리
  • 예외 상황: 기술적 제약이나 강력한 비즈니스 요구사항이 있는 경우에만 예외적으로 다수 애그리거트 수정 허용

리포지토리(Repository)는 애그리거트 루트 단위로만 존재해야 한다.

  • 루트 단위 인터페이스: OrderLineRepository와 같은 하위 엔티티용 리포지토리를 만들지 않고, OrderRepository만 생성
  • 개념적 범위 vs 기술적 진입점: 리포지토리는 애그리거트라는 개념적 범위의 데이터를 다루지만, 기술적으로는 애그리거트 루트의 식별자를 통해 접근하고 관리
  • 데이터 무결성 강제: 루트를 통해서만 영속성 저장소에 접근하게 함으로써 애그리거트 내부의 일관성 경계를 기술적으로 보호
public interface OrderRepository {
Optional<Order> findById(OrderId id);
void save(Order order); // 내부 OrderLine들까지 함께 저장되어 일관성 유지
}

Last updated:

DDD