해당 포스팅은 최범균 작가님의 도메인 주도 개발 시작하기 (P.107~113)를 읽고 정리한 글입니다.
애그리거트 루트
애그리거트 루트의 기능 구현
애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성한다. 예를 들어 Order는 총 주문 금액을 구하기 위해 OrderLine 목록을 사용한다.
public class Order{
private Money totalAmounts;
private List<OrderLine> orderLines;
private void calculateTotalAmounts(){
int sum = orderLines.stream()
.mapToInt(o1 -> o1.getPrice() * o1.getQuantity())
.sum();
this.totalAmounts = new Money(sum);
}
}
애그리거트 루트가 구성요소의 상태만 참조하는 것은 아니다. 기능 실행을 위임하기도 한다. 예를 들어 구현 기술의 제약이나 내부 모델링 규칙 때문에 OrderLine 목록을 별도의 클래스로 분리했다고 해보자.
public class OrderLines {
private List<OrderLine> lines;
public Money getTotalAmounts() {...구현;}
public void changeOrderLines(List<OrderLine> newLines){
this.lines = new Lines;
}
}
이 경우 Order의 changeOrderLines() 메서드는 다음과 같이 내부의 orderLines 필드에 상태 변경을 위임하는 방식으로 기능을 구현한다.
public class Order {
private OrderLine orderLines;
public void changeOrderLines(List<OrderLine> newLines){
orderLines.changeOrderLines(newLines);
this.totalAmounts = orderLines.getTotalAmounts();
}
}
OrderLines는 changeOrderLines()와 getTotalAmounts() 같은 기능을 제공하고 있다.
트랜잭션의 범위
트랜잭션의 범위는 작을수록 좋다. 한 트랜잭션이 한 개 테이블을 수정하는 것과 세 개의 테이블을 수정하는 것을 비교하면 성능에서 차이가 발생한다. 한 개의 테이블을 수정하면 트랜잭션 충돌을 막기 위해 잠그는 대상이 한 개 테이블의 한 행으로 한정되지만, 세 개의 테이블을 수정하면 잠금 대상이 더 많아진다. 잠금 대상이 많아진다는 것은 그만큼 동시에 처리할 수 있는 트랜잭션 개수가 줄어든다는 것을 의미하고 이것은 전체적인 성능(처리량)을 떨어뜨린다.
동일하게 한 트랜잭션에서는 한 개의 애그리거트만 수정해야 한다. 한 트랜잭션에서 두 개 이상의 애그리거트를 수정하면 트랜잭션 충돌이 발생할 가능성이 더 높아지기 때문에 한 번에 수정하는 애그리거트 개수가 많아질수록 전체 처리량이 떨어지게 된다.
한 트랜잭션에서 한 애그리거트만 수정한다는 것은 애그리거트에서 다른 애그리거트를 변경하지 않는다는 것을 의미한다. 한 애그리거트에서 다른 애그리거트를 수정하면 결과적으로 두 개의 애그리거트를 한 트랜잭션에서 수정하게 되므로, 애그리거트 내부에서 다른 애그리거트의 상태를 변경하는 기능을 실행하면 안 된다.
애그리거트는 최대한 서로 독립적이어야 하는데 한 애그리거트가 다른 애그리거트의 기능에 의존하기 시작하면 애그리거트 간 결합도가 높아진다. 결합도가 높아지면 높아질수록 향후 수정 비용이 증가하므로 애그리거트에서 다른 애그리거트의 상태를 변경하지 말아야 한다.
만약 한 트랜잭션으로 두 개 이상의 애그리거트를 수정해야 한다면 애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현해야 한다.
또한 도메인 이벤트를 사용하면 한 트랜잭션에서 한 개의 애그리거트를 수정하면서도 동기나 비동기로 다른 애그리거트의 상태를 변경하는 코드를 작성할 수 있다.
리포지터리와 애그리거트
애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재한다.
애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야 한다. 예를 들어 Order 애그리거트와 관련된 테이블이 세 개라면 Order 애그리거트를 저장할 때 애그리거트 루트와 매핑되는 테이블뿐만 아니라 애그리거트에 속한 모든 구성요소에 매핑된 테이블에 데이터를 저장해야 한다.
동일한 애그리거트를 구하는 리포지터리 메서드는 완전한 애그리거트를 제공해야 한다. 리포지터리가 완전한 애그리거트를 제공하지 않으면 필드나 값이 올바르지 않아 애그리거트의 기능을 실행하는 도중에 NPE와 같은 문제가 발생할 수 있다.
트랜잭션과 관련해 많은 인사이트를 얻었다. 특히 애그리거트 단위 간에도 결합도를 최대한 낮춰야 한다는 점을 깨달았다. 개발을 하거나 어떤 상황에서도 변화의 범위를 최소화하고 시스템의 안정성을 유지하기 위해서는 결합도를 느슨하게 만드는 것이 중요하다. 앞으로 개발할 때 결합도를 어떻게 낮출 수 있을지 끊임없이 고민하며 설계해야겠다고 느꼈다.
'📚 개발자의 서재 > 도메인 주도 개발 시작하기' 카테고리의 다른 글
[DDD] 애그리거트 -4 (0) | 2025.04.29 |
---|---|
[DDD] 애그리거트 -3 (0) | 2025.04.28 |
[DDD] 애그리거트 -1 (0) | 2025.04.26 |
[DDD] 아키텍처 개요 -5 (0) | 2025.04.25 |
[DDD] 아키텍처 개요 -4 (0) | 2025.04.24 |