들어가며
도메인 주도 설계 전문가분들과 대화하거나, MSA 관련 강의를 수강할 때 반드시 언급되는 "애그리거트"에 대해서 명확히 이해하려고 여러 가지 자료들을 찾아보았다. 그러다 최범균 님께서 집필하신 "DDD Start!" 라는 책에 애그리거트 개념이 잘 설명되어 있어서 관련 내용을 공유하고자 한다. (절판된 책이라 구하느라 엄청 힘들었다..)
애그리거트(Aggregate)
애그리거트의 뜻
데이터 변경의 단위로 다루는 연관 객체의 묶음
- 에릭 에반스 | 도메인 주도 설계
연관된 엔티티와 값 객체들의 묶음
- SK C&C | 도메인 주도 설계로 시작하는 마이크로 서비스 개발
애그리거트는 모델이나 비즈니스 도메인을 이해하기 위한 거시적인 방법으로 사용된다. 아래의 다이어그램은 DDD Start! 2장에 나오는 도식을 참고하여 그린 것이다.
개별 객체 수준의 모델만을 참고하여 도메인을 분석하게 되면 무엇이 중요하고 핵심인지 한눈에 파악하기가 힘들다. 하지만 애그리거트 단위로 도메인을 먼저 이해한 후 객체 수준의 모델을 바라보게 된다면 이해하기가 훨씬 쉽다. 아래의 그림은 애그리거트만 표현한 다이어그램이다.
애그리거트의 성질
애그리거트의 핵심은 도메인을 이해하기 쉬운 단순한 구조로 만들어 주는 것이다. 애그리거트는 연관된 개별 객체를 묶어서 만들어지기 때문에 몇 가지 성질을 가진다.
- 애그리거트에 속한 객체는 유사하거나 동일한 생명주기(Life cycle)를 가진다.
주문이라는 행위를 할 때, 주문 객체를 만들면서 주문 항목, 주문한 사람, 배송 정보 등의 객체를 함께 만든다. - 애그리거트는 경계를 가진다.
한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다.
<TIP>
다수의 애그리거트는 한개의 엔티티 객체만 갖는 경우가 많다. 두 개 이상의 엔티티로 구성되는 애그리거트는 드물게 존재한다.
- 최범균, SK C&C 도메인 설계
애그리거트 루트
애그리거트 전체를 관리할 주체를 의미한다. 예제에서는 주문 애그리거트의 주문 엔티티가 루트이다. 애그리거트 루트는 애그리거트의 일관성이 깨지지 않도록 하는것이다. (일관성이란 도메인 규칙을 지키는 것을 의미한다. 예: 주문 총 금액 = 개별 상품의 주문 개수 x 가격) 다음의 샘플 코드가 애그리거트의 일관성을 깨지 않도록 하여 배송 주소를 변경하는 것이다.
Public class 주문 {
public void 변경하다-배송정보(배송주소 배송주소) {
식별하다-아직배송되지않았는지();
변경하다-배송정보();
}
public void 식별하다-아직배송되지않았는지() {
if(상태 != 배송상태.결제대기 && 상태 != 배송상태.기다림) {
throw noew IllegalStateException("already shipped");
}
}
}
애그리거트 루트가 제공하는 메서드는 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야한다. 애그리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다. 일관성을 깨는 직접적인 변경의 예는 다음과 같다.
ShippingInfo si = order.getShippingInfo();
si.setAddress(newAddress);
불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 두 가지 습관이 있어야 한다.
- 단순히 필드를 변경하는 setter를 public 범위로 만들지 않는다.
도메인 로직이 응용 계층이나 표현 계층으로 분산되지 않도록 방지하는 역할을 한다. set 형식 이름 말고 cancel이나 changePassword처럼 의미가 더 잘 드러나는 이름을 사용할 빈도가 높아진다. - VO(Value Object)은 불변으로 구현한다.
VO가 불변이면 VO의 값을 변경하는 방법은 새로운 VO를 전달해서 값을 변경하는 방법밖에 없도록 하여 일관성을 유지한다.
효율적인 애그리거트를 구현하려면 (트랜잭션 범위)
시스템 전체적인 측면에서 보았을 때 트랜잭션 범위는 작을수록 좋다. 한 트랜잭션에서 여러 개의 애그리거트를 수정할수록 전체 처리량이 떨어지기 때문이다. 그러므로 아래의 샘플 코드처럼 한 애그리거트에서 다른 애그리거트를 수정하는 기능을 구현하면 안 된다. (이 코드의 의미는 새배송지 주소를 회원 주소로 업데이트하는 것이다.)
public class 주문 {
private 주문하는사람 주문하는사람;
public void 설정-배송지(배송지주소 새배송지주소, boolean 사용여부-새배송지주소를멤버주소로) {
식별하다-아직배송되지않았는지();
set-배송정보를(새배송지주소);
if (사용여부-새배송지주소를멤버주소로) {
//회원 애그리거트를 건드려 버렸음..
주문하는사람.get-고객().set-배송지주소(새배송지주소);
}
}
}
...
부득이하게 한 트랜잭션으로 두 개 이상의 애그리거트를 수정해야한다면 응용 계층에서 두 애그리거트를 수정하도록 한다. [응용 계층이 무엇인지 모른다면?]
public class ChangeOrderService {
@Transactional
public void change배송정보(OrderId id, 배송정보 새배송정보, boolean 사용여부-새배송지주소를멤버주소로) {
주문 order = orderRepository.findbyId(id);
if(order == null) throw new OrderNotFoundException();
order.shipTo(새배송정보);
if (사용여부-새배송지주소를멤버주소로) {
order.get-주문하는사람.get-고객().change-주소(새배송정보.getAddress());
}
}
}
마치며
이번 글에서는 애그리거트에 대해서 알아보았다. MSA 구현이나 기타 MSA 관련 세미나에서 애그리거트는 아주 기초적인 배경지식으로 다뤄지기 때문에 확실히 이해하고 있는 것이 중요하다. 추후 애그리거트가 가지고 있는 특징들에 대해서도 좀 더 설명하는 글을 작성하려고 한다.
참고자료
[1] 테드의 기술 블로그 | 테드 | 링크
[2] DDD START! | 2016 | 최범균 | 링크
[3] 도메인 주도 설계로 시작하는 마이크로 서비스 개발 | 2021 | 한정헌, 유해식, 최은정, 이주영 | 링크
'MSA 설계 & 도메인주도 설계' 카테고리의 다른 글
[DDD] 도메인 계층의 Structure (0) | 2022.02.08 |
---|---|
[DDD] 인터페이스 계층과 응용 계층의 구현 (0) | 2022.02.07 |
[DDD] 도메인 주도 설계의 계층 분리에 관하여 (0) | 2022.02.03 |
[MSA] MSA 기반 프로젝트 입문 후기 (Devlos feat. @Todo) (1) | 2022.02.02 |
[MSA] 레이어드 아키텍처 vs 헥사고날 아키텍처 vs 클린 아키텍처 요약 (2) | 2022.01.17 |