주난v 개발 성장기

[DDD START!] 3장. 애그리거트 본문

개발 성장기/DDD

[DDD START!] 3장. 애그리거트

주난v 2020. 5. 15. 08:25

애그리거트는 관련된 객체를 하나의 군으로 묶어준다.

애그리거트를 이용하여 모델 간의 관계를 개별 수준뿐만 아니라 상위 수준에서도 이해가능하다.

일관성을 관리하는 기준이 되기도 한다.

 

애그리거트

  • 관련된 모델을 하나로 모은 것

  • 동일한 라이프 사이클

  • Order가 생성되면, OrderLine, ShippingInfo등 애그리거트에 속한 구성 요소는 대부분 함께 생성하고 소멸된다.

  • 애그리거트는 경계를 가진다.

  • 경계의 설정은 도메인 규칙, 요구사항이 중요하다.

    • 하지만, 'A가 B를 갖는다'고 가정한다면 A와 B를 한 애그리거트로 묶어서 생각하기 쉽다.

    • 그러나 그렇지 않다. Product - Review같은 경우는 함께 생성되거나 변경되지 않고, 변경주체도 다르다.

애그리거트 루트

  • 구매할 상품의 개수를 변경하면, OrderLine Entity의 quantity를 변경하고, Order Entity의 totalAmounts를 변경해야 한다.

  • 애그리거트는 여러 객체로 구성되기 때문에, 한 객체만 정상이어서는 안된다. 모든 객체가 정상이어야 한다.

  • 즉, 모든 객체가 일관된 상태를 유지해야하고 애그리거트 전체를 관리하고 책임질 놈이 필요한데...이를 애그리거트 루트라 한다.

도메인 규칙과 일관성

  • 애그리거트 루트가 단순 애그리거트를 포함하는 것이 끝은 아니다.

  • 애그리거트 간의 일관성이 깨지지 않아야한다.

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

//잘 된 코드
Public class Order {
	public void changeShippingInfo(ShippingInfo newShippingInfo) {
    	verifyNotYetShipped();
        setShippingInfo(newShippingInfo);
    }
}
//잘못된 코드

ShippingInfo si = order.getShippingInfo();
si.setAddress(newAdress);

잘못된 코드를 보면, 데이터의 일관성이 깨지고 만약 주문 정보를 변경하기 전에 validation을 체크해야 한다면 해당 코드를 사용하는 모든 부분을 고쳐줘야하는 단점이 있다. 더불어 set 메서드만 넣지 않아도 일관성이 깨질 가능성이 줄어 든다. 

 

애그리거 외부에서 내부 상태를 함부로 바꾸지 못하므로 일관성이 깨질 가능성이 줄어든다.

 

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

  1. 애그리거트 내부의 다른 객체를 조합해서 기능을 완성

  2. 다른 애그리거트에게 위임

public class Member {
	private Password password;
    
    public void changePassword(String currentPassword, String newPassword) {
    	if (!password.match(newPassword) {
        	//Exception
        }
        
        // this.password = 변경;
    }
}

public class OrderLines {
	
    private List<OrderLine> lines;
    
    public void changeOrderLines(List<OrderLine> newLines) {
    	this.lines = newLines;
    }
}

 

트랜잭션

- 한 트랜잭션에서는 한 개의 애그리거트만 수정

- 다른 애그리거트 수정은 불가 (ex. 배송지 정보를 변경하면서, 동시에 회원의 주소를 변경하는 것은 불가)

- 애그리거트는 독립적이어야 하는데, 결합도가 높아지고 수정비용이 증가하고 데이터의 무결성도 깨진다.

- 해야한다면 응용 서비스에서 두 애그리거트를 수정하도록 구현한다. @Transactional

 

리포지터리와 애그리거트

- Repository는 애그리거트 단위로 존재한다.

- Order와 OrderLine을 물리적으로 각각 별도의 DB 테이블에 저장한다고 해서, OrderRepository, OrderLineRepository를 별도로 구현하지 않는다.

- 애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화 해야한다.

- Order를 저장할 때, 다른 것도 같이 저장해야한다.

 

RDBMS를 사용하면 트랜잭션이 보장되서, 저장소에 반영되는 것을 보장

MongoDB는 한 개의 애그리거트를 하나의 문서에 저장함으로 변경을 손실 없이 저장소에 반영할 수 있다.

 

객체 참조가 아닌 ID 참조를 사용하면, 모든 객체가 참조로 연결되지 않고, 한 애그리거트의 속한 객체들만 참조로 연결된다.

Eager, Lazy Load에 대해 고민할 필요가 없다. 즉, 복잡도를 낮추고 한 애그리거트에서 다른 애그리거트를 수정하는 문제를 방지할 수 있다.

 

단점

주문 개수가 10개면 주문을 읽어오기 위한 10번의 쿼리가 실행된다. 이는 지연로딩의 대표적인 문제 N+1이 발생

Join으로 해결을 하면된다. 하지만 이는 애그리거트 간 참조를 ID참조 방식에서 객체 참조 방식으로 되돌리는 것

 

ID 참조방식을 사용하면서, N+1 문제를 해결하려면 전용 조회 쿼리를 커스텀 해야한다.