Java 아키텍처·Spring 용어 사전
아키텍처Aggregate · DDD · 바운디드 컨텍스트 · 도메인 이벤트

애그리거트

함께 변경되어야 하는 객체 묶음과 그 대표. 트랜잭션과 일관성의 경계가 된다.

함께 변경되어야 하는 객체들의 묶음과 그 대표(애그리거트 루트). DDD 전술 설계의 핵심 단위다.

왜 묶는가 — 일관성 경계

주문(Order) ─┬─ 주문항목(OrderItem)
             └─ 배송지(Address)

"주문 총액 = 항목 합계" 라는 규칙을 지키려면
  → 주문과 항목이 '함께' 바뀌어야 한다
  → 이 묶음이 하나의 트랜잭션 단위가 된다

세 가지 규칙

① 바깥에서는 루트를 통해서만 접근한다
   orderRepository.findById(id).addItem(...)   ✅
   orderItemRepository.save(item)              ❌ 규칙을 우회한다
  • ② 애그리거트 하나가 트랜잭션 하나

    • 여러 애그리거트를 한 트랜잭션에서 바꾸지 않는다
  • ③ 다른 애그리거트는 id 로 참조한다

    • 객체 참조가 아니라 식별자만 들고 있는다
@Entity
class Order {
    @Id private Long id;
    @OneToMany(cascade = ALL, orphanRemoval = true)   // 같은 애그리거트
    private List<OrderItem> items;

    private Long memberId;        // ✅ 다른 애그리거트는 id 로
    // @ManyToOne private Member member;   ❌ 경계를 흐린다
}

③이 왜 중요한가

객체 참조로 묶으면

  • 지연 로딩 문제가 애그리거트 경계를 넘어 번진다
  • 한 트랜잭션에서 두 애그리거트를 함께 바꾸기 쉬워진다
  • 나중에 서비스를 분리할 때 참조를 전부 끊어야 한다

id 참조로 두면 경계가 코드에 드러나고, 분리가 쉬워진다

여러 애그리거트를 바꿔야 하면

// 도메인 이벤트로 넘긴다 — 트랜잭션을 나눈다
class Order {
    void complete() {
        this.status = COMPLETED;
        registerEvent(new OrderCompleted(this.id));   // 커밋 후 다른 애그리거트가 처리
    }
}

@TransactionalEventListener(phase = AFTER_COMMIT)
void on(OrderCompleted e) { pointService.grant(e.orderId()); }

최종 일관성을 받아들이는 대신 트랜잭션을 짧게 유지한다.

바운디드 컨텍스트 — 전략 설계

같은 "상품" 이라는 말이 부서마다 다른 것을 가리킨다
  주문 컨텍스트  상품 = 가격·재고
  검색 컨텍스트  상품 = 제목·태그·설명
  • 하나의 거대한 Product 클래스로 합치면 아무도 못 고치는 코드가 된다
  • 컨텍스트마다 자기 모델을 갖고, 경계에서 번역한다

면접 함정

  • "애그리거트는 크게 잡을수록 안전하다" → 크면 락 범위와 트랜잭션이 커져 동시성이 죽는다.
  • "DDD는 폴더 구조" → 도메인 언어와 경계를 정하는 일이 본질이다.

경계를 코드로 강제하기

// 패키지 구조로 경계를 드러낸다
com.example.order
  ├ domain      Order · OrderItem · OrderRepository(인터페이스)
  ├ application OrderService
  └ adapter     JpaOrderRepository · OrderController
// ArchUnit 으로 다른 애그리거트의 내부 접근을 막는다
@Test void 애그리거트_내부는_외부에서_직접_접근하지_않는다() {
    noClasses().that().resideOutsideOfPackage("..order..")
        .should().accessClassesThat().haveSimpleName("OrderItem")
        .check(new ClassFileImporter().importPackages("com.example"));
}

크기를 정하는 기준

"이 규칙을 지키려면 무엇과 무엇이 같은 트랜잭션에서 바뀌어야 하나"

  • 주문 총액 = 항목 합계 — → 주문 + 항목이 한 애그리거트
  • 회원 등급 = 누적 구매액 — → 즉시 일관성이 필요한가? 아니면 이벤트로 충분한가?

작게 잡을수록 동시성이 좋고, 크게 잡을수록 규칙 지키기가 쉽다 망설여지면 작게 잡고 이벤트로 잇는다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 아키텍처 — 왜 계층으로 나누나