Skip to content

Latest commit

 

History

History
135 lines (71 loc) · 5.57 KB

File metadata and controls

135 lines (71 loc) · 5.57 KB

정리

애그리거트 루트는 애그리거트에 속한 모든 객체를 관리하는 주체이다.

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

애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요하다.

이러한 책임을 가지는 것이 애그리거트의 대표 엔티티인 루트 엔티티이다.

3-4

주문 애그리거트에서는 Order 엔티티가 루트 역할을 한다.

OrderLine, ShippingInfo, Orderer 등 주문 애그리거트에 속한 모델을 Order 엔티티에 직접 또는 간접적으로 속한다.

도메인 규칙과 일관성

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

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

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

애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안된다.

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

상태 확인 로직을 응용 서비스에 구현할 수도 있지만 동일한 로직을 응용 서비스에서 중복으로 구현하게 되어 유지 보수성이 떨어진다.

이를 피하기 위해 두 개의 규칙을 반드시 적용해야 한다.

  • 단순히 필드를 변경하는 set 메서드를 공개(public) 범위로 만들지 않는다.
  • 밸류 타입은 불변으로 구현한다.
public class Order {
  public void changeShippingInfo(ShippingInfo newShippingInfo) {
    verifyNotYetShipped();
    setShippingInfo(newShippingInfo);
  }

  // set 메서드의 허용 범위위는 private이다.
  private void setShippingInfo(ShippingInfo newShippingInfo) {
    // 밸류가 불변이면 새로운 객체를 할당해서 값을 변경해야 한다.
    // 불변이므로 this.shippingInfo.setAddress(newShippingInfo.getAddress())와 같은 코드를 사용할 수 없다.
    this.shippingInfo = newShippingInfo;
  }
}

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

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

public class OrderLines {
  private List<OrderLine> lines;
  
  public Money 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;
  }
}

다음과 같이 구현 기술의 제약이나 내부 모델링 규칙 때문에 OrderLine 목록을 별도 클래스로 분리했다고 가정했다.

이 경우 Order의 changeOrderLines 메서드는 내부의 orderLines 필드에 상태 변경을 위임한다.

하지만 이렇게 구현하면 OrderLine 목록이 바뀌는데 총합이 계산되지 않을 수 있다.

이를 막기 위해 OrderLines 객체를 불변으로 구현하거나 OrderLines의 변경 기능을 패키지나 protected 범위로 한정하면 된다.

트랜잭션 범위

트랜잭션 범위는 작을수록 좋다.

하나의 테이블을 수정하면 트랜잭션 충돌을 막기 위해 잠그는 대상이 하나의 테이블의 한 행으로 한정되지만, 여러 개의 테이블을 수정하면 잠금 대상이 많아진다.

잠금 대상이 많아진다는 것은 동시에 처리하는 트랜잭션 수가 줄어든다는 것을 의미하고 이것은 전체적인 성능(처리량)을 떨어뜨린다.

한 트랜잭션에서는 하나의 애그리거트만 수정해야 한다.

하나의 애그리거트에서 다른 애그리거트를 수정하면 결과적으로 여러 개의 애그리거트를 한 트랜잭션에서 수정하게 되므로, 애그리거트 내부에서 다른 애그리거트의 상태를 변경하는 기능을 실행하면 안 된다.

부득이하게 하나의 트랜잭션에서 여러 개의 애그리거트를 수정해야 한다면 응용 서비스에서 수정하도록 구현한다.

다음과 같은 경우에 하나의 트랜잭션에서 여러 개의 애그리거트를 변경하는 것을 고려할 수 있다.

  • 팀 표준 - 팀이나 조직의 표준에 따라 사용자 유스케이스와 관련된 응용 서비스의 기능을 한 트랜잭션으로 실행해야 하는 경우가 있다.
  • 기술 제약 - 기술적으로 이벤트 방식을 도입할 수 없는 경우 한 트랜잭션에서 다수의 애그리거트를 수정해서 일관성을 처리해야 한다.
  • UI 구현의 편리 - 운영자의 편리함을 위해 주문 목록 화면에서 여러 주문의 상태를 한 번에 변경하고 싶을 것이다. 이 경우 하나의 트랜잭션에서 여러 주문 애그리거트의 상태를 변경해야 한다.

느낀점

지금껏 하나의 트랜잭션에서 여러 개의 애그리거트를 변경해왔다.

직접 트랜잭션 충돌 현상에 대해 경험해보고 트러블 슈팅해본 적이 없기 때문인 것 같다.

애그리거트의 일관성을 지키는 것은 중요하기 때문에 많은 경우를 고려해 최대한 하나의 트랜잭션에서 하나의 애그리거트만 변경하도록 구현할 것이다.