Order
와 주문 상품 개수를 포함하는 OrderLine
상품
과 리뷰
-> 함께 생성, 변경되지도 않고 변경 주체(상품 담당자, 고객)도 다르기 때문에 서로 다른 애그리거트에 속함
- 불필요한 중복 없이 애그리거트 루트를 통해서만 도메인 로직 구현하기
- 단순히 필드를 변경하는 set 메서드를 공개(public) 범위로 만들지 않는다.
- 도메인 로직이 분산되므로, 유지 보수하기 힘듦
- 밸류 타입은 불변으로 구현한다.
- 애그리거트 외부에서 밸류 객체 상태 변경 불가
public class Order {
private ShippingInfo shippingInfo;
public void changeShippingInfo(ShippingInfo newShippingInfo) {
verifyNotYetShipped();
setShippingInfo(newShippingInfo);
}
//set메서드의 접근 허용 범위는 private이다.
private void setShippingInfo(ShippingInfo newShippingInfo) {
//밸류가 불변이면 새로운 객체를 할당해서 값을 변경해야 함
//불변이므로 this.shippinfInfo.setAddress(newShippingInfo.getAddress())와 같은 코드 사용 불가능
this.shippingInfo = newShipping;
}
...
}
애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성함
애그리거트 루트가 구성요소의 상태만 참조하는 것이 아닌, 기능 실행을 위임하기도 함
팀 표준이나 구현 기술의 제약으로 객체를 불변으로 구현할 수 없는 경우 -> 객체 변경 기능을 패키지
나 protected
범위로 한정
public class ChangeOrderService {
//두 개 이상의 애그리거트를 변경해야 하면, 응용 서비스에서 각 애그리거트의 상태를 변경한다
@Transactional
public void changeShippingInfo(OrderId id, ShippingInfo newShippingInfo,
boolean useNewShippingAddrAsMemberAddr) {
Order order = orderRepository.findbyId(id);
if (order == null)
throw new OrderNotFoundException();
order.shipTo(newShippingInfo);
if (useNewshippingAddrAsMemberAddr) {
Member member = findMember(order.getOrderer());
member.changeAddress(newShippingInfo.getAddress());
}
}
...
}
다음 경우에는 한 트랜잭션에서 두 개 이상의 애그리거트 변경하는 것을 고려
- 팀 표준 : 팀이나 조직의 표준에 따라 사용자 유스케이스와 관련된 응용 서비스의 기능을 한 트랜잭션으로 실행해야 하는 경우
- 기술 제약 : 기술적으로 이벤트 방식을 도입할 수 없는 경우 한 트랜잭션에서 다수의 애그리거트를 수정해서 일관성을 처리해야 함
- UI 구현의 편리 : 운영자의 편리함을 위해 주문 목록 화면에서 여러 주문의 상태를 한 번에 변경하고 싶을 경우.
Order
는 애그리거트 루트고, OrderLine
은 애그리거트에 속하는 구성요소이므로 Order
를 위한 리포지터리만 존재save
: 애그리거트 저장findById
: ID로 애그리거트를 구함Order
애그리거트와 관련된 테이블이 세 개라면, Order
애그리거트를 저장할 때 애그리거트 루트와 매칭되는 테이블뿐만 아니라 애그리거트에 속한 모든 구성요소에 매핑된 테이블에 데이터 저장지연(lazy) 로딩
과 즉시(eager) 로딩
(p.121 ~ 124 예제코드 참고, 살짝 헷갈리므로 다시 읽어보기,,)
예시 상황: 특점 상점이 신고당해서 더 이상 물건을 등록하지 못함
public class RegisterProductService {
public ProductId registerNewProduct(NewProductRequest req) {
Store store = storeRepository.findById(req.getStoreId());
checkNull(store);
if (!store.isBlocked()) {
throw new StoreBlockedException();
}
ProductId id = productRepository.nextId();
Product product = new Product(id, store.getId(), ...);
productRepository.save(product);
return id;
}
}
public class Store {
public Product createProduct(ProductId id, newProductId, ... ) {
if (isBlocked())
throw new StoreBlockedException();
return new Product(newProductId, getId(), ...);
}
}
public class RegisterProductService {
public ProductId registerNewProduct(NewProductRequest req) {
Store store = storeRepository.findStoreById(req.getStoreId());
checkNull(store);
ProductId id = productRepository.nextId();
Product product = store.createProduct(id, store.getId(), ...);
productRepository.save(product);
return id;
}
}
createProduct()
는 Product 애그리거트를 생성하는 팩토리 역할+) 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트 생성해랴 하는 경우 -> 애그리거트에 팩토리 메서드 구현