본문 바로가기

JAVA/DDD

Aggregate 애그리거트

반응형

Aggregate 애그리거트


애그리거트 루트

-총 금액인 totalAmounts를 갖고 있는  Order 엔티티

-개별 구매 상품의 개수인 quantity와 금액인 price를 갖고 있는 OrderLine  밸류


구매할 상품의 개수를 변경하려면 한  OrderLine의 qunantity를 변경하고 totalAmount도 변경해야된다.


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

도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야한다.

주문 애그리거트의 경우 OrderLine을 변경하면 Order의  totalAmounts도 다시 계산해서 총 금액이 맞아야한다.


애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리 할 주체가 필요한데, 이 책임을 지는 것이 바로 애그리거트의 루트 엔티티이다.

애그리거트 루트 엔티티는 애그리거트의 대표 엔티티로 애그리거트에 속한 객체는 애그리거트 루트 엔티티에 직접 또는 간접적으로 속한다.


도메인 규칙과 일관성

애그리거트 루트의 핵심 역할은 애그리거트의 일관성이 깨지지 않도록 하는 것.

애그리거트 루트는 애그리거트가 제공해야 할 도메인 기능을 구현한다.


 public class Order {

// 애그리거트 루트는 도메인 규칙을 구현한 기능을 제공한다.

public void changeShippingInfo(ShippingInfo newShippingInfo) {

verifyNotYetShipped();

setShippingInfo(newShippingInfo);

}


private verifyNotYetShipped() {

if(state != OrderState.PAYMENT_WAITING && state !=OrderState.WAITING)

throw new IllegalStateException("aleady shipped");

}

}


애그리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다.

애그리거트 루트가 강제로 규칙을 적용할 수 없어 모델의 일관성을 깨는 원인이 된다.


 ShippingInfo si = order.getShippingInfo();

si.setAddress(newAddress);

애그리거트 루트인 Order에서 ShippingInfo를 가져와 직접 정보를 변경하고 있다.

주문 상태에 상관없이 배송지 주소를 변경할 수 있는데, 이는 업무 규칙을 무시하고 직접 DB 테이블에 직접 데이터를 수정하는 것과 같은 결과를 만든다.

일관성을 지키키위해 상태 확인 로직을 응용 서비스에 구현 할 수 있지만, 이렇게 되면 동일한 검사 로직을 여러 응용 서비스에서 중복해서 구현할 가능성이 높아져 샇왕이 더 악화된다.


// 주요 도메인 로직이 중복되는 문제

ShippingInfo si = order.getShippingInfo();

if(state != OrderState.PAYMENT_WAITING && state !=OrderState.WAITING){

throw new IllegalStateException("aleady shipped");

}

si.setAddress(newAddress);

불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 도메인 모델의 두가지 습관적으로 적용해야 한다.


1) 단순히 필드를 변경하는 set 메서드를 공개public 범위로 만들지 않는다.

2) 밸류 타입은 뷸변으로 만든다.


//도메인 모델에서 공개 set 메서드는 가급적 피해야 한다.

public void setName(String name) {

this.name = name;

}

공개 set 메서드는 중요 도메인의 의미나 의도를 표현하지 못하고 도메인 로직이 도메인 객체가 아닌 응용 영역이나 표현 영역으로 분산되게 만드는 원인이 된다.

도메인 로직이 한곳에 응집되어 있지 않게 되므로 코드를 유지보수 할 때에도 분석하고 수정하는데 더 많은 시간을 들이게 된다.


밸류는 불변타입으로 구현한다.

밸류 객체의 값을 변경 할 수 없으면 애그리거트 루트에서 밸류 객체를 구해도 값을 변경 할 수 없기 때문에 애그리거트 외부에서 밸류 객체의 상태를 변경 할 수없게 된다.


ShippingInfo si = order.getShippingInfo();

si.setAddress(newAddress); // ShippingInfo 밸류 객체가 불변이면, 이 코드는 컴파일 에러!

밸류객체가 불변이면 밸류 객체의 값을 변경하는 방법은 새로운 밸류 객체를 할당하는 것


 public class Order {

// 애그리거트 루트는 도메인 규칙을 구현한 기능을 제공한다.

public void changeShippingInfo(ShippingInfo newShippingInfo) {

verifyNotYetShipped();

setShippingInfo(newShippingInfo);

}


// set  메서드의 접근 허용 범위는 private

private setShippingInfo(ShippingInfo newShippingInfo) {

//밸류가 불변이면 새로운 객체를 할당해서 값을 변경해야 한다.

// 불변이므로  this.shippingInfo.setAddress(newShippingInfo.getAddress()) 와 같은 코드를 사용할 수없다.

this.shippingInfo = newShippingInfo;

}

}

밸류 타입의 내부 상태를 변경하려면 애그리거트 루트를 통해서만 가능하다.


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

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


 public class Order {

private Money totalAmounts;

private List<OrderLine> orderLines;


private void calculateTotalAmounts() {

int sum = orderLines.stream()

.mapToInt(ol -> ol.getPrice() * ol.quantity())

.sum();

this.totalAmounts = new Money(sum);

}

}


 public class Member {

private String password;


public void changePassword(String currentPassword, String newPassword) {

if(!password.match(currentPassword)) {

throw new PassworldNotMatchException();

}

this.password = newPassword;

}

}



애그리거트 루트가 구성요소의 상태만 참조하는 것은 아니다.

기능 실행 위임하기도 한다.


구현 기술 제약이나 내부 모델링 규칙 때문에 OrderLIne 목록을 별도 클래스로 분리했다고 하자

 public class OrderLines {

private List<OrderLine> lines;


public int getTotalAmounts(){


}


public void changeOrderLines(List<OrderLine> newLines) {

this.lines = newLines;

}

}


 public class Order {

private OrderLines orderLines;


public void changeOrderLines(List<OrderLine> newLines) {

orderLines.changeOrderLines(newLines);

this.totalAmounts = orderLines.getTotalAmounts();

}

}


Order의 changeOrderLines() 메서드는 내부의 orderLines 필드에 상태 변경을 위임하는 방식으로 구현한다.

OrderLines는 changeOrderLines()와 getTotalAmounts() 같은 기능을 제공하고 있다.

만약 Order가 getOrderLines()와 같이 OrderLines를 구할 수 있는 메서드를 제공하면 애그리거트 외부에서 OrderLines의 기능을 실행할 수 있게 된다.


 OrderLInes lines = order.getOrderLines();


//외부에서 애그리거트 내부 상태 변경

// order의 totalAmounts가 값이 OrderLines가 일치하지 않게 됨

lines.changeOrderLines(newOrderLines);

이 코드는 주문의 OrderLine 목록이 바뀌는데 총합은 계산하지 않는 버그를 만든다.

애그리거트 외부에서 OrderLine 목록을 변경할 수 없도록 OrderLines를 불변으로 구현하면된다.


팀 표준이나 구현 기술의 제약으로 OrderLines를 불변으로 구현할 수 없다면 OrderLines의 변경 기능을 패키지나 protected 범위로 한정해서 외부에서 실행 할수 없도록 제한하는 방법이 있다.

보통 한 애그리거트에 속한 모델은 한 패키지에 속하기 때문에 패키지나 protected 범위를 사용하면 애그리거트 외부에서 상태 변경 기능을 실행하는 것을 방지 할 수 있다.


DDD START! 도메인주도설꼐구현과 핵심개념 익히기 저자-최범균

반응형

'JAVA > DDD' 카테고리의 다른 글

리포지터리와 모델구현(JPA 중심)  (0) 2017.12.23
트랜잭션 범위  (0) 2017.12.23
아키텍처  (0) 2017.12.18
엔티티와 밸류  (0) 2017.12.16
도메인 모델 도출  (0) 2017.12.15