[DDD START] 3장 애그리거트

애그리거트

도메인 규칙과 일관성

애그리거트 루트

애그리거트의 일관성을 유지하기 위한 관리 주체

public class Order {
    private ShippingInfo shippingInfo;

    // 애그리거트 루트는 도메인 규칙을 구현한 기능을 제공한다
    public void changeShippingInfo(ShippingInfo newShippingInfo) {
        verifyNotYetShipped();
        setShippingInfo(newShippingInfo);
    }

    private void verifyNotYetShipped() {
        if (state != OrderState.PAYMENT_WAITING && state != OrderState.WAITING) 
            throw new IllegalStateException("already shipped");
    }

    // set 메서드의 접근 허용 범위는 private이다
    // value를 불변으로 만들어 변경 시 새로운 객체를 할당하게 한다
    private void setShippingInfo(ShippingInfo newShippingInfo) {
        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();
    }
}

애그리거트 루트는 내부의 다른 객체에 기능을 위임한다

public class Order {
    private OrderLines orderLines;
    
    public void changeOrderLines(List<OrderLine> newLines) {
        orderLines.changeOrderLines(newLines);
        this.totalAmounts = orderLines.getTotalAmounts();
    }
}

트랜잭션 범위

리포지터리와 애그리거트

리포지터리는 애그리거트 단위로 존재

애그리거트는 개념적으로 하나

애그리거트 간 참조

필드를 통해 참조

public class orderer {
    private Member member;
    private String name;
}
order.getOrderer().getMember().getId()

ID를 이용해서 다른 애그리거트를 참조

public class Orderer {
    private MemberId memberId;
}

// ID를 이용해서 참조하는 애그리거트를 구한다
Customer customer = customerRepository.findById(
    order.getOrderer().getCustomerId()
);
ID를 이용한 참조와 조회 성능
조회성능을 높이려면 전용 조회 쿼리를 사용해야 한다
@Repository
public class JpaOrderViewDao implements OrderViewDao {
    @Override
    public List<OrderView> selectByOrderer(String ordererId) {
        String selectQuery = 
        "select .....
        join ....";
    }
}

애그리거트를 팩토리로 사용하기

애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메서드를 구현하는것을 고려한다

예)온라인 쇼핑몰에서 고객이 신고를 해서 특정 상점이 더 이상 물건을 등록하지 못하도록 차단한 상태를 구현

public class RegisterProductService {

    public ProductId registerNewProduct(NewProductRequest req) {
        Store account = accountRepository.findStoreById(req.getStoreId());
        checkNull(account);
        if (account.isBlocked()) {
            throw new StoreBlockedException();
        }
        ProductId id = productRepository.nextId();
        Product product = new Product(id, account.getId(), ...);
        productRepository.save(product);
        return id;
    }
}

위 코드는 Product를 생성 가능한지 판단하는 코드와 Product를 생성하는 코드가 분리되어 있어서 나빠 보이지 않지만 중요한 도메인 로직 처리가 응용서비스에 노출되었다

이 도메인 기능을 Store 애그리거트에서 팩토리 메서드로 구현한다

public class Store extends Member {

    public Product createProduct(ProductId newProductId, ...) {
        if (isBlocked()) throw new StoreBlockedException();
        return new Product(newProdcutId, getId, ...)
    }
}
public class RegisterProductService {

    public ProductId registerNewProduct(NewProductRequest req) {
        Store account = accountRepository.findStoreById(req.getStoreId());
        checkNull(account);
        ProductId id = productRepository.nextId();
        Product product = account.createProduct(id, ...);
        productRepository.save(product);
        return id;
    }
}

Reference