문제 상황
핫딜 시스템을 설계하면서 Product와 Event 간의 다대다 관계를 어떻게 처리할지 고민이 되었습니다. 하나의 상품은 여러 이벤트에 참여할 수 있고, 하나의 이벤트에는 여러 상품이 포함될 수 있는 구조였습니다.
단순히 중간 테이블을 만들어 해결하려고 했지만, 비즈니스 로직을 구현하면서 예상치 못한 문제들이 발생했습니다.
초기 시도: 별도 애그리거트로 분리
설계 변경
HotDealProduct를 독립적인 애그리거트로 분리해보았습니다.


새로운 문제 발생
1. 애그리거트 루트로서의 정체성 부족
HotDealProduct가 독립적인 도메인 개념인지 의문이 들었습니다. 비즈니스적으로 봤을 때 이는 Event의 일부분으로 보는 것이 더 자연스러웠습니다.
2. 데이터 일관성 문제
3. 비즈니스 규칙 구현의 복잡성
"이벤트 종료 시 모든 관련 HotDealProduct도 비활성화" 같은 비즈니스 규칙을 구현할 때 여러 애그리거트에 걸친 복잡한 로직이 필요했습니다.
최종 해결방안: Event 애그리거트로 통합
핵심 인사이트
문제를 다시 분석해보니 핵심 인사이트를 얻을 수 있었습니다:
- 비즈니스 관점에서 HotDealProduct는 Event의 일부
- Event가 HotDealProduct의 생명주기를 완전히 관리
- Product는 시스템에서 가장 많은 요청을 처리하는 도메인으로 순수하게 유지
- Event가 애그리거트 루트로서 모든 할인 관련 비즈니스 규칙을 담당

최종 설계

애그리거트 루트로서의 Event 역할

애그리거트 루트 선정 기준
이번 경험을 통해 애그리거트 루트 선정에 대한 중요한 기준들을 정리할 수 있었습니다:
1. 생명주기 관리 책임
// Event가 HotDealProduct의 전체 생명주기를 관리
public class Event {
public void addProducts() { ... } // 생성
public void updateDiscount() { ... } // 수정
public void removeProducts() { ... } // 삭제
public void endEvent() { ... } // 종료
}
2. 비즈니스 불변조건 보장
// 이벤트 관련 모든 비즈니스 규칙을 Event가 담당
public class Event {
public void addProductsToEvent(List<Long> productIds) {
validateEventIsActive(); // 활성 이벤트만 상품 추가 가능
validateProductLimit(productIds); // 상품 수량 제한
validateNoDuplicates(productIds); // 중복 상품 방지
// 실제 추가 로직
}
}
3. 트랜잭션 경계
// 하나의 트랜잭션으로 일관성 보장
@Transactional
public void updateEventDiscount(Long eventId, int newDiscount) {
Event event = eventRepository.findById(eventId);
// 애그리거트 내 모든 관련 엔티티가 함께 업데이트
event.updateDiscount(newDiscount);
// HotDealProduct들의 할인가격도 자동으로 재계산됨
}
해결된 문제들
1. 명확한 객체 그래프
AS-IS: Product ↔ HotDealProduct ↔ Event (양방향, 복잡)
TO-BE: Event(Root) → HotDealProduct(Entity), Event → Product(외부 참조)
2. 단일 진입점을 통한 일관성 보장
모든 HotDealProduct 관련 작업이 Event 애그리거트 루트를 통해서만 가능하므로 비즈니스 규칙이 항상 적용됩니다.
3. 트랜잭션 경계의 명확화
Event 애그리거트 내의 모든 변경사항이 하나의 트랜잭션으로 처리되어 데이터 일관성이 보장됩니다.
4. Product 도메인의 순수성 유지
Product는 상품 관리 기능에만 집중할 수 있게 되어, 높은 트래픽을 효율적으로 처리할 수 있습니다.
결론
애그리거트 루트를 선정할 때는 다음 기준들을 종합적으로 고려해야 합니다:
- 생명주기 관리: 누가 엔티티의 생성/수정/삭제를 책임지는가?
- 비즈니스 불변조건: 어떤 도메인이 비즈니스 규칙을 보장해야 하는가?
- 트랜잭션 경계: 어느 범위까지 데이터 일관성을 보장해야 하는가?
- 도메인 개념의 자연스러움: 비즈니스 관점에서 어느 쪽이 더 직관적인가?
- 시스템 성능과 확장성: 전체 아키텍처에서 어떤 영향을 미치는가?
이번 경험을 통해 단순히 ERD 관점에서만 접근하지 않고, 애그리거트 루트의 역할과 책임을 명확히 정의하는 것이 얼마나 중요한지 깨달았습니다. 특히 다대다 관계에서 중간 엔티티의 소속을 결정할 때는 비즈니스 도메인의 본질을 깊이 이해하고, 해당 엔티티가 어떤 애그리거트의 생명주기와 비즈니스 규칙에 더 밀접하게 연관되어 있는지를 파악하는 것이 핵심이라고 생각합니다.
'Spring 7기 프로젝트 > 플러스 팀 프로젝트' 카테고리의 다른 글
| Spring Boot에서 RestTemplate 내부 API 호출 시 JWT 토큰 전달하기 (2) | 2025.07.11 |
|---|---|
| 도메인 간 의존성 낮추기 : API 호출 및 스프링 이벤트 구독을 위한 Common 패키지 설계 (0) | 2025.07.10 |
| 도메인주도 설계 및 데이터 흐름 시각화하기 (1) | 2025.07.10 |