Aggregate Pattern

Aggregate 패턴은 마이크로서비스 아키텍처(MSA)에서 데이터 일관성을 유지하기 위한 중요한 패턴 중 하나이다.
이 패턴은 도메인 주도 설계(DDD)에서 유래했으며, 복잡한 도메인 모델을 관리하고 트랜잭션 경계를 정의하는 데 도움을 줍니다.

Aggregate는 하나의 루트 엔티티(Aggregate Root)와 관련된 객체들의 집합이다.
이 집합은 하나의 단위로 취급되며, 데이터 일관성을 유지하는 경계 역할을 한다.

Aggregate 패턴을 효과적으로 사용하려면 도메인에 대한 깊은 이해와 지속적인 리팩토링이 필요하다.
이 패턴을 통해 마이크로서비스 아키텍처에서 데이터 일관성을 유지하면서도 확장 가능하고 유지보수가 용이한 시스템을 구축할 수 있다.

Aggregate Pattern
https://codeopinion.com/aggregate-ddd-isnt-hierarchy-relationships/

Aggregate의 구성 요소

  • Aggregate Root: Aggregate의 최상위 엔터티로, 외부에서 Aggregate에 접근할 때의 진입점이다. 모든 데이터 변경은 Aggregate Root를 통해 이루어진다.

  • 엔터티 및 값 객체: Aggregate Root 아래에 포함된 관련 엔터티나 값 객체들로, Aggregate 내부에서만 접근되고 관리된다.

Aggregate 패턴의 주요 특징

  1. 트랜잭션 일관성: Aggregate는 ACID(원자성, 일관성, 격리성, 지속성) 특성을 갖는다.
  2. 명확한 경계: Aggregate는 외부 객체와의 경계가 명확하다.
  3. 내부 객체 보호: 외부 객체는 오직 Aggregate Root를 통해서만 내부 객체에 접근할 수 있다.
  4. 불변성 유지: Aggregate Root는 내부 객체들의 무결성을 보장해야 한다.

Aggregate 패턴의 구현 방법

  1. Aggregate Root 식별: 도메인 모델에서 핵심 엔티티를 Aggregate Root로 선정한다.
  2. 경계 설정: Aggregate에 포함될 관련 엔티티와 값 객체를 정의한다.
  3. 일관성 규칙 정의: Aggregate 내부의 일관성을 유지하기 위한 비즈니스 규칙을 정의한다.
  4. 접근 제어: Aggregate Root를 통해서만 내부 객체에 접근할 수 있도록 구현한다.
  5. 트랜잭션 관리: Aggregate 단위로 트랜잭션을 관리한다.

Aggregate 패턴의 장점

  1. 데이터 일관성 향상: Aggregate 내부의 객체들은 항상 일관된 상태를 유지한다.
  2. 복잡성 관리: 큰 도메인 모델을 작은 단위로 나누어 관리할 수 있다.
  3. 성능 최적화: Aggregate 단위로 데이터를 로드하고 저장하여 성능을 개선할 수 있다.
  4. 동시성 제어: Aggregate 단위로 락을 적용하여 동시성 문제를 해결할 수 있다.

Aggregate 패턴의 주의사항

  1. Aggregate 크기: Aggregate를 너무 크게 설계하면 성능 문제가 발생할 수 있으므로 적절한 크기로 유지해야 한다.
  2. 참조 관리: Aggregate 간 참조는 ID를 통해 이루어져야 하며, 직접적인 객체 참조는 피해야 한다.
  3. 일관성 범위: 모든 데이터를 즉시 일관되게 유지할 필요는 없습니다. 일부 데이터는 결과적 일관성(Eventual Consistency)으로 관리할 수 있다.
  4. 설계 복잡성: Aggregate 패턴을 적용하면 설계가 복잡해질 수 있으므로, 도메인 전문가와의 긴밀한 협력이 필요하다.

실제 사용 예시

예를 들어, 전자상거래 시스템에서 ‘주문(Order)’ Aggregate를 설계할 수 있다:

  1. Order를 Aggregate Root로 정의한다.
  2. OrderItem, ShippingAddress 등을 Order Aggregate에 포함시킨다.
  3. Order를 통해서만 OrderItem을 추가하거나 수정할 수 있도록 구현한다.
  4. Order의 총액 계산, 주문 상태 변경 등의 비즈니스 로직을 Order 클래스에 구현한다.
    이렇게 설계하면 주문과 관련된 모든 데이터의 일관성을 쉽게 유지할 수 있다.

예시코드

전자상거래 시스템

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Aggregate Root 예시
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> orderItems;
    private OrderStatus status;
    private Money totalAmount;

    // Aggregate Root를 통한 생성자
    public Order(CustomerId customerId) {
        this.id = new OrderId();
        this.customerId = customerId;
        this.orderItems = new ArrayList<>();
        this.status = OrderStatus.CREATED;
        this.totalAmount = Money.ZERO;
    }

    // Aggregate 내부 상태 변경은 항상 Root를 통해 이루어짐
    public void addItem(ProductId productId, int quantity, Money unitPrice) {
        validateAddItem(quantity);
        OrderItem newItem = new OrderItem(productId, quantity, unitPrice);
        orderItems.add(newItem);
        recalculateTotalAmount();
    }

    private void validateAddItem(int quantity) {
        if (quantity <= 0) {
            throw new IllegalArgumentException("수량은 0보다 커야 합니다.");
        }
        if (status != OrderStatus.CREATED) {
            throw new IllegalStateException("주문 생성 상태에서만 상품을 추가할 수 있습니다.");
        }
    }

    private void recalculateTotalAmount() {
        this.totalAmount = orderItems.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
}

참고 및 출처