본문 바로가기
스터디/도메인 주도 개발 시작하기

[도메인 주도 개발 시작하기] 03. 애그리거트

by 디토20 2022. 7. 3.
반응형

 

 

 

[도메인 주도 개발 시작하기] 03. 애그리거트

 

 

1. 애그리거트

- 온라인 쇼핑몰 시스템을 개발할 때 아래와 같이 상위 수준 개념을 이용해서 전체 모델을 정리하면 전반적인 관계를 이해하는 데 도움이 된다.

 

위의 상위 수준 모델을 개별 객체 단위로 다시 그려보면 아래와 같다. 상위 모델에 대한 이해 없이 개별 객체 단위 수준에서 개념을 파악하려면 더 오랜 시간이 걸린다.

백 개 이상의 테이블을 한 장의 ERD에 표시하면, 개별 테이블 간의 관게를 파악하느라 전반적인 구조나 큰 수준에서의 도메인 간의 관게를 파악하기 어려워져, 코드를 변경하고 확장하는 것이 어려워진다. 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위 수준에서 모델을 조망할 수 있는 방법이 필요한데, 그 방법이 바로 애그리거트다.

 

위는 개별 객체 수준 모델을 애그리거트 단위로 묶어서 다시 표현한 것이다. 애그리거트를 사용함으로써 모델 간의 관계를 개별 모델 수준과 상위 수준에서 모두 이해할 수 있다.

  • 애그리거트는 일관성을 관리하는 기준이 된다.
  • 복잡한 도메인을 단순한  구조로 만들어주어, 도메인 기능을 확장하고 변경하는데 효율적이다.
  • 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.
  • 한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다.
  • 애그리거트는 독립된 객체 군으로 각 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다.

 

 

1.1 A가 B를 갖을 경우 같은 애그리거트일까?

흔히 'A가 B를 갖는다'로 설계할 수 있는 요구사항이 있다면 A와 B를 한 애그리거트로 묶어서 생각하기 쉽다. 하지만 이것은 언제나 참은 아니다.

 

좋은 예로 상품리뷰를 생각해보자. 상품 상세페이지에 들어가면 상품 상세 정보와 함께 리뷰 내용을 보여줘야할 때 상품 엔티티와 리뷰 엔티티가 한 애그리거트에 속한다고 생각할 수 있다. 그러나 상품과 리뷰는 함께 생성되지 않고, 함께 변경되지도 않는다. 게다가 상품을 변경하는 주체가 관리자라면, 리뷰를 생성하고 변경하는 주체는 고객이다.

 

 

리뷰의 변경이 상품에 영향을 주지 않고, 반대로 상품의 변경이 리뷰에 영향을 주지 않기 때문에 이 둘은 다른 애그리거트라고 볼 수 있다. 처음에는 큰 애그리거트로 보이는 것들이 많지만, 도메인 규칙을 제대로 이해할 수록 애그리거트의 실제 크기는 줄어들고, 일반적으로 하나의 애그리거트는 하나의 엔티티만을 갖는다.

 

 

 

 

2. 애그리거트 루트

애그리거트는 여러 객체로 구성되기 때문에 한 객체만 상태가 정상이면 안된다. 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야 한다. 애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요한데, 이 책임을 지는 것이 바로 애그리거트 루트 엔티티이다. 애그리거트 루트 엔티티는 애그리거트의 대표 엔티티로써, 애그리거트에 속한 객체는 애그리거트 루트 엔티티에 직접 또는 간접적으로 속하게 된다.

 

 

2.1 도메인 규칙과 일관성

  • 애그리거트 루트의 핵심 역할은 애그리거트의 일관성이 깨지지 않도록 하는 것
  • 애그리거트 루트는 애그리거트가 제공해야 할 도메인 기능을 구현한다.
  • 애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안된다.
  • 단순히 필드를 변경하는 set 메서드를 공개 범위로 만들지 않는다.
  • 밸류 타입은 불변으로 구현한다.

 

2.2 애그리거트 루트의 기능 구현

  • 애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성한다.
  • 예를 들어 Order는 총 주문 금액을 구하기 위해 OrderLine 목록을 사용한다.

 

2.3 트랜잭션 범위

트랜잭션 범위는 작을수록 좋다. 한 트랜잭션에서는 한 개의 애그리거트만 수정해야 한다. 이것은 애그리거트에서 다른 애그리거트를 변경하지 않는다는 것을 의미한다. 애그리거트 내부에서 다른 애그리거트의 상태를 변경하는 기능을 실행하면 안된다. 부득이하게 한 트랜잭션으로 두 개 이상의 애그리거트를 수정해야 한다면 애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현한다.

 

 

 

3. 리포지터리와 애그리거트

애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재한다. Order와 OrderLine을 물리적으로 각각 별도의 DB테이블에 저장한다고 해서 Order와 OrderLine을 위한 리포지터리를 각가 만들지는 않는다. 단지 애그리거트 루트인 Order 리포지터리만 존재한다.

 

애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야한다. 동일하게 애그리거트를 구하는 리포지터리 메서드는 완전한 애그리거트를 제공해야 한다.

 

 

 

4. ID를 이용한 애그리거트 참조

한 객체가 다른 객체를 참조하는 것처럼 애그리거트도 다른 애그리거트를 참조한다. 애그리거트간의 참조는 필드를 통해 쉽게 구현할 수 있다.

ORM 기술을 이용하면 애그리거트 루트에 대한 참조를 쉽게 구현할 수 있고 필드를 이용한 애그리거트 참조를 사용하면 다른 애그리거트의 데이터를 쉽게 조회할 수 있다. 하지만 필드를 이용한 애그리거트 참조는 아래의 문제를 야기할 수 있다.

  • 편한 탐색 오용
  • 성능에 대한 고민
  • 확장 어려움

애그리거트의 직접 참조 편리함때문에 다른 애그리거트를 쉽게 수정하고자 하는 유혹에 빠지기 쉽다. 또한 애그리거트를 직접 잠조하면 지연로딩과 즉시로딩등 성능과 관련된 여러가지 고민을 해야한다. 세번째로 서비스가 커지면 도메인을 분리하기 위해 시스템을 분리하기 시작하면서 더이상 다른 애그리거트 루트를 참조하기 위해 JPA와 같은 단일 기술을 사용해 확장을 할 수 없다는 문제점이 발생할 수 있다.

 

이런 세가지 문제점을 완화시키기 위해 ID를 이용해 다른 애그리거트를 참조할 수 있다. DB 테이블에서 외래키로 참조하는 것과 비슷하게 ID를 이용한 참조는 다른 애그리거트를 참조할 때 ID를 사용한다.

ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체들만 참조로 연결된다. 이는 애그리거트의 경계를 명확히 하고 애그리거트간의 물리적인 연결을 제거하기때문에 모델의 복잡도를 낮춰준다. 또한 ID를 이용한 참조방식은 외부 애그리거트를 직접 참조하지 않기 때문에 다른 애그리거트를 수정하는 문제를 근원적으로 방지할 수 있다.

728x90
반응형

댓글