AbstractAggregateRoot

1. AbstractAggregateRoot란?
Spring Data에서 제공하는 도메인 이벤트 발행을 위한 추상 클래스입니다. 애그리거트 루트가 이를 상속받으면 도메인 이벤트를 쉽게 등록하고 자동으로 발행할 수 있습니다.
import org.springframework.data.domain.AbstractAggregateRoot;
public class Order extends AbstractAggregateRoot<Order> {
public void pay() {
this.status = PAID;
registerEvent(new OrderPaidEvent(this.id));
}
}
2. 핵심 역할
2.1 도메인 이벤트 저장소
public abstract class AbstractAggregateRoot<A> {
private final List<Object> domainEvents = new ArrayList<>();
protected <T> T registerEvent(T event) {
this.domainEvents.add(event);
return event;
}
}
도메인 모델이 발생시킨 이벤트를 임시로 보관합니다.
2.2 Spring 이벤트 시스템과 자동 연결
// 도메인에서는 이벤트만 등록
order.pay(); // 이벤트 등록됨
// Repository save 시 자동 발행
orderRepository.save(order); // 이벤트가 자동으로 발행됨
Spring Data JPA가 save() 호출 시 등록된 이벤트를 자동으로 발행합니다.
2.3 트랜잭션 안전성 보장
@Transactional
public void processOrder() {
order.pay(); // 이벤트 등록만 됨
// 여기서 예외 발생하면 이벤트 발행 안됨
orderRepository.save(order); // 커밋 시점에 이벤트 발행
}
```
이벤트 발행이 save()와 함께 트랜잭션 내에서 관리됩니다.
## 3. 동작 흐름
```
1. 도메인 로직 실행
└─> registerEvent()로 이벤트 등록
2. Repository.save() 호출
└─> Spring Data가 domainEvents 조회
3. ApplicationEventPublisher로 이벤트 발행
└─> 등록된 모든 이벤트 발행
4. clearDomainEvents() 호출
└─> 이벤트 목록 초기화
3. 실전 사용 패턴
단일 이벤트
public class Order extends AbstractAggregateRoot<Order> {
public void pay() {
validatePayment();
this.status = PAID;
registerEvent(new OrderPaidEvent(this.id));
}
}
다중 이벤트
public class Order extends AbstractAggregateRoot<Order> {
public void cancel() {
validateCancellation();
this.status = CANCELLED;
registerEvent(new OrderCancelledEvent(this.id));
registerEvent(new OrderStatusChangedEvent(this.id, CANCELLED));
}
}
조건부 이벤트
public class Order extends AbstractAggregateRoot<Order> {
public void complete() {
this.status = COMPLETED;
if (isFirstOrder()) {
registerEvent(new FirstOrderCompletedEvent(this.customerId));
} else {
registerEvent(new OrderCompletedEvent(this.id));
}
}
}
4. Spring 의존성 논쟁
순수주의 관점
문제점
- 도메인이 org.springframework.data.domain에 의존
- 프레임워크 독립성 위배
- 헥사고날 아키텍처 원칙 위반
대안: 순수 구현
// 도메인 (Spring 의존 없음)
public class Order {
private List<DomainEvent> events = new ArrayList<>();
public void pay() {
this.status = PAID;
events.add(new OrderPaidEvent(...));
}
public List<DomainEvent> getDomainEvents() {
return events;
}
}
// 인프라 (Spring은 여기서만)
@Component
public class OrderRepositoryAdapter {
public Order save(Order order) {
Order saved = jpaRepository.save(order);
for (DomainEvent event : saved.getDomainEvents()) {
eventPublisher.publishEvent(event);
}
saved.clearDomainEvents();
return saved;
}
}
실용주의 관점
AbstractAggregateRoot의 장점
- 특정 기술에 종속되지 않는 일반적 추상화
- DB 독립적 (JPA, MongoDB, Redis 모두 동작)
- 도메인 개념(애그리거트 루트)을 정확히 표현
- 이벤트 발행 자동화로 실수 방지
업계 전문가 의견
- Martin Fowler: "완벽한 순수성보다 실용적인 타협이 더 나을 때가 많다"
- Vaughn Vernon: "충분히 일반적인 추상화이며 대부분의 프로젝트에서 문제없다"
- Eric Evans: "절대적인 규칙은 아니다. 실용적인 타협점을 찾아라"
5. 선택 기준
AbstractAggregateRoot 사용 (권장)
다음 상황에서 사용:
- 일반적인 Spring 프로젝트
- 팀 생산성이 중요
- 프레임워크 전환 계획 없음
- 실용성이 순수성보다 우선
순수 구현 사용
다음 상황에서 사용:
- 멀티 프레임워크 지원 필요
- 도메인 모델을 다른 프로젝트에서 재사용
- 헥사고날 아키텍처 엄격 준수 필요
- 완벽한 프레임워크 독립성 요구
6. 의존성 레벨 비교
| 의존성 | 심각도 | 설명 |
| AbstractAggregateRoot | 낮음 | 일반적 추상화, 도메인 개념 표현 |
| @Entity, @Id | 중간 | JPA 표준이지만 기술 의존 |
| Hibernate, MongoDB 클라이언트 | 높음 | 특정 구현체 강결합, 피해야 함 |
7. 결론
대부분의 Spring 프로젝트에서 AbstractAggregateRoot 사용을 권장합니다.
이유:
- 충분히 일반적이고 안전한 추상화
- 생산성 향상과 실수 방지
- Spring 생태계의 사실상 표준
- 실용적 이점이 이론적 순수성보다 중요
다만, 프레임워크 독립성이 핵심 요구사항인 특수한 경우에는 순수 구현을 고려할 수 있습니다.
핵심은 완벽한 순수성보다 실용적인 균형입니다.
'Spring > 이론' 카테고리의 다른 글
| Redis를 활용한 데이터베이스 캐싱 전략 (0) | 2025.10.23 |
|---|---|
| Spring Boot 환경에서의 직렬화(Serialization) 이해하기 (3) | 2025.10.22 |
| Entity 식별자 전략: UUID vs Auto Increment vs Snowflake (0) | 2025.10.01 |
| Spring Boot 검증 처리 아키텍처 전략: 컨트롤러에서 BindingResult를 직접 처리하지 말아야 하는 이유 (0) | 2025.09.16 |
| 데이터베이스 인덱스의 구조와 특징 (0) | 2025.06.18 |