Aggregate 분리를 고민한 이유
이커머스 개발을 위한 설계를 진행하면서 시퀀스 다이어그램을 그리는 과정에서 Aggregate에 대해 공부하고 고민하게 되었다.
“이 도메인을 하나의 Aggregate로 묶을까, 아니면 분리할까?”
시작은 “어떤 Service에 요청해야 할까?” 였다
처음에는 단순히 기능 흐름을 정리하려고 시퀀스 다이어그램을 그리고 있었다.
그런데 어느 순간 이런 의문이 들었다.
“브랜드 정보를 가져올 때, ProductService에 요청해야 할까?
아니면 BrandService에 직접 요청해야 할까?”
예를 들어, 상품 목록을 조회할 때는 당연히 ProductService를 떠올린다.
하지만 실제 화면에는 브랜드 이름과 설명 같은 정보도 함께 필요하다.
그렇다면 ProductService가 Brand 엔티티까지 직접 책임져야 할까?
아니면 BrandService에 별도로 요청하는 것이 맞을까?
Aggregate를 나눌 때 기준
Aggregate 내부에 속한 엔티티들은 한 번의 트랜잭션 안에서 함께 일관성을 유지해야 한다.
이 말은 곧, Aggregate에 속한 모든 엔티티를 한꺼번에 다루며 저장하고 변경해야 한다는 뜻이다.
1. Aggregate 내부는 한 번에 일관성을 보장해야 한다
1) Brand를 Aggregate Root로 삼고 그 안에 수많은 Product를 포함시킨다면?
- Brand를 수정할 때마다 Product들도 모두 같은 트랜잭션에서 관리해야 한다.
- Brand를 조회할 때 Product까지 한꺼번에 로딩해야 할 수도 있다.
하나의 Brand에 수천, 수만 개의 Product가 연결돼 있다면 트랜잭션 범위가 지나치게 넓어지고 성능과 관리 부담이 커진다.
2) Brand와 Product를 분리한 것처럼, Product와 Like도 분리한다
비슷한 고민을 Product와 Like(좋아요) 관계에서도 하게 된다.
- Like는 사용자가 Product를 좋아요 눌렀다는 기록이다.
- Like를 취소하거나 추가한다고 해서 Product의 가격이나 재고 같은 핵심 속성이 함께 수정될 필요는 없다.
따라서 Like도 별도의 Aggregate로 두고 productId로만 느슨하게 연결한다.
Like를 등록할 때 ProductService에 요청하는 것이 아니라 별도의 LikeService를 두어 요청한다.
2. Brand와 Product는 라이프사이클이 다르다
1) Brand와 Product는 관리하는 흐름이 다르다
- Brand는 브랜드명, 설명, 노출 상태를 관리하는 흐름이 있다.
- Product는 가격, 상태를 관리하는 흐름이 있다.
Brand를 수정한다고 Product들이 함께 수정되는 것은 아니다.
반대로 Product를 추가하거나 변경한다고 Brand가 영향을 받지도 않는다.
서로 다른 라이프사이클을 억지로 묶는 것은 트랜잭션 경계를 부자연스럽게 만든다.
2) 반대로 Order와 OrderLine은 왜 같은 Aggregate인가?
한편, 반대의 예시도 있다. Order(주문) 와 OrderLine(주문 상품 상세) 관계다.
- Order를 저장할 때 그 안에 포함된 OrderLine들도 함께 저장해야 한다.
- OrderLine은 Order가 존재하지 않으면 의미가 없다. (독립적으로 존재할 수 없다.)
- Order의 상태가 바뀌면 OrderLine들의 상태도 함께 일관성을 맞춰야 한다.
이처럼 Order와 OrderLine은 동일한 라이프사이클을 공유하고 반드시 함께 일관성을 유지해야 하므로 하나의 Aggregate로 묶는 것이 자연스럽다.
그래서 주문한 상품의 상세 정보 조회시 OrderLine은 OrderService에 요청하게 된다.
User -> OrderService -> OrderRepository (Order와 OrderLine을 함께 관리)
3. Aggregate 간에는 ID로만 연결한다
DDD에서는 Aggregate 간의 참조 방식에 대해서도 권장 사항이 있다.
Aggregate 간에는 ID로만 연결하고, 객체 그래프를 깊게 연결하지 않는다.
따라서 Product가 Brand를 참조할 때는 Brand 엔티티 자체를 직접 들고 있지 않고, brandId 같은 식별자만 저장한다. 클래스 다이어그램을 그릴 때도 Aggregate 여부에 따라 이렇게 표현했다. 서로 다른 Aggregate라면 id 같은 식별자로 연결하고, 같은 Aggregate 내부라면 해당 엔티티를 속성으로 직접 가진 것으로 표시하였다.
'Concept > 클린코드' 카테고리의 다른 글
[클린코드] 단위테스트 (1) | 2025.05.29 |
---|