🧱 1. Aggregate: 객체들의 운명 공동체
데이터베이스 테이블을 설계할 때 가장 많이 하는 실수가 “모든 테이블을 FK로 엮어버리는 것"입니다. 그러면 어디까지 조회해야 하고, 어디까지 저장해야 하는지 경계가 사라집니다.
Aggregate는 **“함께 생성되고, 함께 변경되고, 함께 죽는 객체들의 묶음”**입니다.
경계의 시각화
classDiagram
namespace OrderAggregate {
class Order {
+confirm()
+cancel()
-List~OrderLine~ lines
-ShippingInfo shipping
}
class OrderLine {
-productId
-quantity
}
class ShippingInfo {
-address
-receiver
}
}
namespace UserAggregate {
class User {
+changeName()
}
}
Order "1" *-- "N" OrderLine : Composition
Order "1" *-- "1" ShippingInfo : Composition
Order ..> User : Referenced by ID only!
Order,OrderLine,ShippingInfo는 한 묶음입니다.- **주문(Order)**이 이 구역의 대장(Aggregate Root)입니다.
- 외부에서는
OrderLine에 직접 접근하면 안 됩니다. 오직Order를 통해서만 명령을 내려야 합니다.
📏 2. 트랜잭션의 원칙: One Transaction, One Aggregate
가장 중요한 규칙입니다. “하나의 트랜잭션에서는 하나의 Aggregate만 수정해야 합니다.”
❌ 나쁜 설계: 거대 트랜잭션
@Transactional
public void orderAndChangeAddress(OrderId id, String newAddr) {
Order order = orderRepo.findById(id);
User user = userRepo.findById(order.getUserId());
order.shipTo(newAddr); // Order 수정
user.changeAddress(newAddr); // User 수정 (???)
}
- 이러면
Order테이블과User테이블에 동시에 락(Lock)이 걸립니다. - 트래픽이 늘어나면 DB가 터지는 지름길입니다.
✅ 좋은 설계: 결과적 일관성 (Eventual Consistency)
“주문이 완료되면, 사용자 등급을 올려준다"는 요구사항이 있다면?
sequenceDiagram
participant OrderSvc as Order Aggregate
participant EventBus
participant UserSvc as User Aggregate
OrderSvc->>OrderSvc: 1. 주문 완료 (Commit)
OrderSvc->>EventBus: 2. Publish "OrderCompleted"
EventBus->>UserSvc: 3. Subscribe Event
UserSvc->>UserSvc: 4. 등급 상향 (New Tx)
- Order 트랜잭션: 주문 상태 변경 ->
OrderCompleted이벤트 발행 -> 커밋. - 비동기 처리: 이벤트를 받아서 별도의 트랜잭션으로 User를 수정.
핵심: 두 변경 사이에 수 밀리초의 지연이 있지만, 시스템은 훨씬 유연하고 빨라집니다.
요약
- Aggregate Root: 문지기. 외부에서는 오직 Root하고만 대화해라.
- 참조: 다른 Aggregate는 객체가 아니라 ID로 참조해라. (Lazy Loading 지옥 탈출)
- 이벤트: 다른 Aggregate를 바꿔야 하면 도메인 이벤트를 던져라.
💬 댓글