객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재


개별 객체 수준에서 모델을 바라보면 상위 수준에서 관계를 파악하기 어렵다.
애그리거트
복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만든다.

복잡도가 낮아지는 만큼 도메인 기능을 확장하고 변경하는 데 필요한 노력도 줄어든다.
한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.
‘A가 B를 갖는다’로 해석할 수 있는 요구사항이 있다고 하더라도 이것이 반드시 A와 B가 한 애그리거트에 속한다는 것을 의미하는 것은 아니다.
도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야한다.
애그리거트 루트
애그리거트 전체를 관리할 주체

애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안된다.
불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만드려면?
public class Order {
private Shippinginfo shippinginfo;
public void changeShippingInfo(ShippingInfo newShippinglnfo) {
verifyNotYetShipped();
setShippinglnfo(newShippinglnfo);
}
// set 메서드의 접근 허용 범위는 private
private void setShippingInfo(ShippingInfo newShippinglnfo) {
// 밸류가 불변이면 새로운 객체를 할당해서 값을 변경해야 한다.
this.shippinginfo = newShippinglnfo;
}애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성한다.
트랜잭션 범위는 작을수록 좋다.
public class Order {
private Orderer orderer;
public void shipTo(ShippingInfo newShippinglnfo,
boolean useNewShippingAddrAsMemberAddr) {
verifyNotYetShipped();
setShippinglnfo(newShippinglnfo);
if (useNewShippingAddrAsMemberAddr) {
// 다른 애그리거트의 상태를 변경하면 안 됨!
orderer.getMember().changeAddress(newShippingInfo.getAddress());
}애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현한다.public class ChangeOrderService {
// 응용 서비스에서 각 애그리거트의 상태를 변경한다.
@Transactional
public void changeShipping!nfo(OrderId id. Shippinginfo newShippinglnfo,
boolean useNewShippingAddrAsMemberAddr) {
Order order = orderRepository.findbyld(id);
if (order == null) throw new OrderNotFoundException();
order.shipTo(newShippinglnfo);
if (useNewShippingAsMemberAddr) {
Member member = findMember(order.getOrdererO);
member.changeAddress(newShippinglnfo.getAddressO);
}
}
...도메인 이벤트를 사용하면 한 트랜잭션에서 한 개의 애그리거트를 수정하면서도 동기나 비동기로 다른 애그리거트의 상태를 변경하는 코드를 작성할 수 있다.조회를 위한 별도의 DAO를 만들고 조인을 이용해 한 번의 쿼리로 조회
@Repository
public class JpaOrderViewDao implements OrderViewDao {
@PersistenceContext
private EntityManager em;
@0verride
public List<OrderView> selectByOrderer(String ordererld) {
String selectQuery =
"select new com.myshop.order.application.dto.OrderViewCOj m, p) "+
"from Order o join o.orderLines ol. Member m. Product p " +
"where o.orderer.memberld .id = :ordererld "+
"and o.orderer.memberld = m.id "+
"and index(ol) = 0 " +
"and ol.productld = p.id "+
"order by o.number.number desc";
TypedQuery<OrderView> query =
em.createQuery(selectQuery, OrderView.class);
query.setParameter("ordererld", ordererld);
return q니ery.getResultl_ist();
}
}
한 대의 DB 장비로 대응할 수 없는 수준의 트래픽이 발생하는 경우 조회 성능을 높이기 위해 캐시를 적용하거나 조회 전용 저장소를 따로 구성
public class Product {
private CategoryId categoryId;
}public class RegisterProductService {
public Productld registerNewProduct(NewProductRequest req) {
Store store = storeRepository.findByld(req.getStoreldO);
checkNull(store);
Productld id = productRepository.nextId()
// 생성하는 메서드에서 검증까지 시도
Product product = store.createProduct(id, ...생략);
productRepository.save(product);
return id;
}
}팩토리 메서드를 추가하면 Product를 생성할 때 필요한 데이터의 일부를 직접 제공하면서 동ProductFactory에 위임하는 방법도 있다.