DDD 의 Aggregate

DDD 의 Aggregate

본 글에서는 도메인 주도 설계(Domain Driven Design) 에서 굉장히 중요한 개념인 애그리거트(Aggregate)에 대해 알아본다.

간략한 설명은 DDD 시작하기 에서 다룬다. 본 포스팅에 없는 내용도 있으니 함께 읽는것을 추천한다.

Aggregate 란?

애그리거트(Aggregate)는 한마디로 서로 관련이 있는 도메인 모델들의 집합이다.

많은 수의 도메인 모델 간의 복잡한 관계를 파악하기란 쉬운 일이 아니다.

그렇기 때문에 서로 관련이 있는 도메인 모델들 끼리 묶어 각 도메인 모델의 상세 구현보다는 더 큰 그림으로 도메인 모델간의 관계를 파악하는것이 좋다.

자세한 사항이 궁금하면 그 때 애그리거트 내부를 살펴보면 된다.

대부분의 경우 하나의 애그리거트는 하나의 엔티티와 여러개의 밸류로 구성된다. 드물게 하나의 애그리거트에 두개의 엔티티가 존재하기도 한다.

각 애그리거트에는 애그리거트 루트라는 도메인 엔티티가 하나씩 있다.

애그리거트 루트는 애그리거트 내에 속한 객체의 변경을 책임지며, 도메인 규칙에 따라 언제나 애그리거트 내 모든 도메인 모델들의 일관성을 유지할 책임이 있다.

애그리거트는 DB 에 도메인을 저장하거나 읽어들이는 단위이며, 애그리거트를 읽을 때는 애그리거트 루트의 id 를 이용한다.

일반적으로 하나의 애그리거트에는 하나의 도메인 엔티티(애그리거트 루트)가 존재하며, 0개 이상의 밸류 타입이 존재한다.

드물게 한 애그리거트에 2개 이상의 엔티티가 존재하지만, 사실은 애그리거트 루트를 제외한 도메인 모델이 엔티티가 아니라 밸류 타입이거나 다른 애그리거트에 속해야 하는 경우가 많으므로 잘 확인해야한다.

서로 다른 도메인 모델이 변경의 주체와 생성 및 변경의 시점이 같다면 같은 애그리거트에 속할 가능성이 높다.

Aggregate 와 트랜잭션

하나의 트랜잭션에서 둘 이상의 애그리거트를 수정하는 것은 성능상 좋지 않아 피하는 것이 좋다.

그러기 위해서는 하나의 애그리거트에서 다른 애그리거트를 변경하지 말아야 한다는 이야기가 된다.

(물론, 그러지 말아야 하는 또다른 이유는 그러한 행위가 애그리거트간의 결합도를 높이기 때문이기도 하다)

만약, 하나의 트랜잭션에서 둘 이상의 애그리거트를 변경해야 한다면 보통은 이벤트나 비동기를 사용하여 이를 피할 수 있으며,

이벤트나 비동기를 사용할 수 없는 경우에는 다른 애그리거트의 변경을 응용 서비스에서 수행하여 적어도 한 애그리거트에서 다른 애그리거트를 변경하는 것만은 피해야한다.

Aggregate 의 참조

또하나 주의 해야 할 점은, 하나의 애그리거트 내에서 필드를 통해 다른 애그리거트를 직접 참조하면 다음과 같은 문제가 발생한다는 것이다.

  • 객체 그래프 탐색이 쉬움
    • 즉, 다른 애그리거트를 수정하기가 쉬움
  • 성능에 대한 고민이 필요
    • 지연 로딩/즉시 로딩
  • 확장의 어려움
    • 애그리거트1은 MySQL, 애그리거트2는 MongoDB, 애그리거트3은 Redis 에 저장

필드 대신 id 를 통해 애그리거트를 간접적으로 참조한다면 위와같은 문제가 모두 해결된다.

특정 애그리거트가 필요할 때마다 id 를 사용하여 Repository 를 통해 애그리거트를 가져오는것이다.

대신 이렇게 하면 N+1 과같은 문제를 해결하기 위해 JPQL 등으로 한 번에 데이터를 불러오는 조인 쿼리를 별도로 만들어줄 필요는 있다.

Aggregate 간 연관 관계

JPA 를 처음 접하는 경우 흔히 하는 실수가 객체의 모든 연관 관계를 지연 로딩과 즉시 로딩으로 어떻게든 처리하고 싶은 욕구에 사로잡히는 것이다.

하지만 위에서 설명한 바와 같이, 애그리거트는 Id 를 통해 간접적으로 참조하는 것이 좋으며,

1:N 이나 M:N 연관 관계를 가지는 객체라고 하더라도 이를 그대로 실제로 구현에 반영하는 경우는 드물다.

예를 들어, User 와 User 가 작성한 Post 간의 관계는 1:N 관계이지만, 보통은 이 User 가 작성한 모든 Post 를 한 번에 불러오는 것이 아니라 페이징을 이용해 조금씩 불러오게 된다.

또한, Category 와 Product 간의 관계는 M:N 관계이지만, 하나의 Product 가 속한 Category 는 한 번에 보여줘도, 한 Category 에 속하는 모든 Product 를 한 번에 보여주지는 않기 때문에 단방향 M:N 연관만 적용하게 된다.

Aggregate 와 팩토리

하나의 애그리거트가 다른 애그리거트를 생성하는 팩토리의 역할을 할 수도 있다.

예를 들어, 다른 유저의 신고로 인해 특정 유저가 일정 기간 댓글을 작성할 수 없다고 가정하자.

그럼 특정 유저 id 를 가진 경우 댓글 엔티티를 생성하는 것을 막아야할 것이다.

이 때, User 의 상태에 따라 Comment 생성 가능 여부를 판단해야하며, 이와 동시에 Comment 애그리거트를 생성할 때는 어차피 User 의 id 가 필요하기 때문에

User 클래스에 팩토리 메서드를 추가하여 User 애그리거트를 Comment 애그리거트의 팩토리로 사용하면 응용 서비스로 도메인 로직이 분산되는 것을 막고, 도메인의 응집도를 높일 수 있다.

Comments