본문 바로가기

관심사/독후감

최범균 <DDD START! > 3장 요약

3장. 애그리거트

복잡한 도메인 객체를 이해하고, 관리하기 쉬운 단위로 만들려면, 상위 수준에서 모델을 조망할 수 있는 방법이 필요하다.
수많은 객체를 애그리거트로 묶어서 생각하면, 좀 더 상위 수준에서 도메인간의 관계를 이해하는 데에 도움이 된다.

그리고 애그리거트는 일관성을 관리하는 기준이 되어서, 복잡한 도메인을 단순한 구조로 만들어준다.

위의 다이어그램처럼 애그리거트는 서로 경계가 있다. 한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다. 
애그리거트는 독립적이기 때문에, 다른 애그리거트를 관리하지 않는다. 

이러한 경계는 도메인 규칙과 요구사항과 관련있다. 도메인 규칙에 따라 함께 생성되는 구성요소는 한 애그리거트에 속할 가능성이 높다. 그래서 한 애그리거트에 속하는 객체들의 라이플사이클은 거의 유사하다. 

애그리거트 루트

애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면, 전체 애그리거트를 관리할 수 있는 루트 애그리거트가 필요하다. 
루트 애그리거트는 애그리거트의 일관성이 깨지지 않게 하는 역할을 한다.

예를 들어, 주문 애그리거트는 배송지 변경, 상품 변경과 같은 기능을 제공하는데, 애그리거트 루트인 Order가 이 기능을 구현한 메서드를 제공한다.

일관성을 위하여, 불필요한 중복코드를 없애고, 애그리거트 루트를 통해서만 도메인을 구현하려면 
도메인 모델에서는 아래의 두가지 규칙을 가져가는게 좋다.

1. 단순히 필드를 변경하는 set 메서드를 pubic 범위로 공개하지 않는다.
   => 애그리거트 외부에서는 내부 상태를 함부로 바꿀 수 없어야한다.
2. 밸류 타입은 불변으로 구현한다.
   => 그래야 이상한데서 밸류를 set 같은 명령문으로 바꿔버리는 상황이 발생하지 않는다. 


트랜잭션 범위

한 애그리거트에서는 다른 애그리거트를 변경하면 안된다. 
만약, 주문 한건 발생했을 때, 유저의 회원등급을 높여줘야하는 상황인 경우,
주문 애그리거트에서 유저 애그리거트를 변경하면 안된다.
이 경우, 별도의 서비스 레이어에서 각각의 애그리거트 함수를 호출해야한다. 

애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현한다. 그래서 객체를 저장하는 리포지터리는 애그리거트 단위로 존재한다. 

예를 들어 "광고서빙"이라는 애그리거트가 있다고 생각해보자.
여기에는 광고와 광고 타겟팅 애그리거트가 속해있고, 각각 별도의 테이블로 관리된다고 생각해보자. 
이 경우, 리포지터리는 광고 / 광고 타겟팅별로 각각 존재하는게 아니다. 이 경우, "광고서빙"이라는 한 개념의 리포지터리로 관리된다.

즉.. 타겟팅 정보를 바꾸려면, 광고 서빙 리포지터리를 통해서 타겟팅 정보를 바꿔야한다는거다. 

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

 참고 : (슬라이드 쉐어) ddd start ch3

애그리거트 내부에서, 다른 애그리거트를 직접 참조하는 방법은 좋지 않다.

이렇게하면, 다른 애그리거트를 직접 접근하고, 수정하기 쉬워져서

애그리거트 간의 의존 결합도가 높아지기 때문이다.

그리고 만약 하위 도메인 별로 다른 DBMS를 사용해야하는 경우, Active Record 같은 단일 기술을 사용할 수 없게 된다. 

Order.find_by_id(id).member 

이런식으로 사용하고 있었다면... 단일 DB를 사용해야한다. 만약 애그리거트마다 사용하는 DBMS가 다르다면, 위의 구조는 사용할 수 없다. 


그러나 ID를 참조하는 형태로 하면, 이러한 문제들을 해결할 수 있다. 

order = Order.find_by_id(id) member = MongoDB::Member.find_by_id(order.member_id)

이런 식으로 사용하면, 하나의 DBMS에 제약되지 않는다.


애그리거트를 팩토리로 사용하기
애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야한다면, 
애그리거트에 팩토리 메서드를 구현하는게 좋다.