도입
DDD를 공부하다 보면 Entity, Value Object보다 Aggregate(애그리거트)에서 가장 많이 막힙니다.
이유는 개념 자체가 “클래스 구조”가 아니라 비즈니스 규칙 + 동시성 + 트랜잭션과 연결되기 때문입니다.
특히 실무에서는 “연관관계가 보이니까 다 묶자”라고 설계하면 금방 거대한 엔티티가 되고, 성능/락/변경 복잡도가 폭발합니다. 그래서 Aggregate는 객체 그래프의 크기가 아니라 일관성 경계(consistency boundary)로 이해하는 것이 핵심입니다.
“애그리거트는 함께 저장해야 하는 객체 묶음이 아니라,
한 번의 변경에서 반드시 지켜야 하는 규칙의 경계이다.”
정의
애그리거트에는 항상 외부에서 접근하는 대표 객체가 존재하며, 이를 Aggregate Root(애그리거트 루트)라고 합니다. 외부는 루트를 통해서만 내부 상태를 변경해야 하고, 루트는 그 과정에서 도메인 규칙(불변조건)을 검사합니다.
💡 TIP / “관계가 있다” ≠ “같은 Aggregate”
Order와 Member, Product가 서로 연관되어 있어도 반드시 하나의 Aggregate일 필요는 없습니다. 한 트랜잭션 안에서 동시에 강한 일관성으로 묶어야 하는가?가 기준입니다.
구성 요소
핵심 규칙
1. 불변조건(Invariant)은 Aggregate 내부에서 지킨다
예를 들어 주문의 총액은 주문 항목 합계와 같아야 한다, 취소된 주문은 배송 시작 상태가 될 수 없다 같은 규칙은 컨트롤러나 UI가 아니라 Order Aggregate 내부 행동에서 보장해야 합니다.
2. 트랜잭션 경계는 Aggregate 단위로 잡는다
일반적으로 한 번의 명령(Command)에서 강한 일관성이 필요한 변경은 한 Aggregate 안에서 끝내는 것이 좋습니다. 여러 Aggregate를 한 트랜잭션에 강하게 묶기 시작하면 락 경쟁, 결합도 증가, 확장성 저하가 빠르게 나타납니다.
3. 외부 Aggregate는 객체 참조보다 ID 참조를 우선한다
Order가 Member, Product를 직접 객체로 들고 있으면 경계가 흐려지고 연쇄 로딩 문제가 생깁니다. 따라서 보통은 memberId, productId 같은 식별자만 보관하고, 필요한 정보는 애플리케이션 서비스나 조회 모델에서 조합합니다.
예시
Order Aggregate에 포함될 수 있는 것
- Order (Root) : 주문 상태, 주문번호, 생성일시, 취소/확정 행위
- OrderLine (Entity) : 상품별 수량/금액/할인 정보
- Money / Address (VO) : 금액, 배송지
Order Aggregate에 보통 직접 포함하지 않는 것
- Member Aggregate 전체 (memberId로 참조)
- Product Aggregate 전체 (productId로 참조)
- Inventory Aggregate 전체 (재고 차감은 별도 경계에서 처리, 필요 시 이벤트 활용)
주문 취소 규칙 예시
- 배송 시작 전까지만 취소 가능
- 이미 취소된 주문은 다시 취소할 수 없음
- 취소 시 주문 상태 + 취소 시각 + 취소 사유가 함께 기록되어야 함
이런 규칙은 Order.cancel(...) 같은 루트 메서드에서 검사/변경해야 안전합니다.
설계 기준
1. 가능한 한 작게 유지하기
Aggregate가 커질수록 한 번의 변경에 읽고 잠그고 저장해야 하는 데이터가 늘어나 동시성 성능이 나빠집니다. “같이 조회된다”는 이유만으로 묶지 말고, 같이 변경되어야 하는가를 기준으로 판단합니다.
2. 조회 모델과 명령 모델 분리 고려하기
화면에 보여주기 위해 필요한 데이터 조합은 복잡할 수 있지만, 그것을 그대로 Aggregate에 넣을 필요는 없습니다. 명령(Command)에서는 규칙 중심 모델을 사용하고, 조회(Query)는 별도 DTO/읽기 모델로 최적화하면 설계가 훨씬 깔끔해집니다.
3. Aggregate 간 협력은 이벤트로 느슨하게 연결하기
주문 생성 후 재고 차감, 알림 발송, 포인트 적립 등은 다른 Aggregate/컨텍스트의 책임일 수 있습니다. 이때 한 트랜잭션에 모두 묶기보다 도메인 이벤트를 발행해 후속 처리를 분리하면 확장성과 변경 대응력이 좋아집니다.
JPA 실무 포인트
자주 하는 오해
BAD
- Order가 Member, Product, Inventory를 객체로 모두 소유
- 내부 엔티티를 외부 서비스에서 직접 수정
- 단순 조회 편의 때문에 경계를 확장
- 트랜잭션 하나에 여러 Aggregate 변경을 습관적으로 묶음
GOOD
- 루트를 통해서만 상태 변경
- 불변조건을 루트/도메인 로직에서 보장
- 다른 경계는 ID로 참조
- 후속 처리는 이벤트로 분리해 느슨하게 연결
설계 체크리스트
✅ 점검 질문
- ✔️ 이 객체들은 한 번의 변경에서 함께 강한 일관성이 필요한가?
- ✔️ 외부가 내부 엔티티를 직접 수정하지 못하게 루트가 통제하고 있는가?
- ✔️ 조회 편의 때문에 Aggregate가 비정상적으로 커지지 않았는가?
- ✔️ 다른 Aggregate와의 연관은 ID 참조로 충분하지 않은가?
- ✔️ 동시성/락/성능 문제까지 고려했을 때 현재 경계가 현실적인가?
정리
✅ 핵심 요약
- ✔️ Aggregate는 단순 묶음이 아니라 일관성 경계입니다.
- ✔️ 외부는 Aggregate Root를 통해서만 내부 상태를 변경해야 합니다.
- ✔️ 다른 Aggregate는 객체 참조보다 ID 참조를 우선하는 것이 일반적입니다.
- ✔️ Aggregate는 가능하면 작게 유지하고, 후속 처리는 이벤트로 분리하는 것이 좋습니다.
- ✔️ 좋은 Aggregate 설계는 도메인 규칙뿐 아니라 성능/동시성/유지보수성까지 함께 개선합니다.