7.1 여러 애그리거트가 필요한 기능

한 애그리거트로 기능을 구현 할 수 없을 때가 있는데 그 대표적인 예가 결제 금액 계산 로직이다.

public class Order {
	...
	private Orderer orderer;
	private List<OrderLine> orderLines;
	private List<Coupon> usedCoupons;

	private Money calculatePayAmounts() {
		Money totalAmounts = calculateTotalAmount();
		// 쿠폰별로 할인 금액을 구한다.
		Money discount = 
					coupons.stream()
									.map(coupon -> calculateDiscount(coupon))
									.reduce(Money(0), (v1,v2) -> v1.add(v2);
		// 회원에 따른 추가 할인을 구한다.
		Money membershipDiscount = 
				calculateDiscount(orderer.getMember.getGrade());
		//실제 결제 금액 계산
		return totalAmounts.minus(discount).minus(membershipDiscount);
	}

	private Money calculateDiscount(Coupon coupon){
		//orderLines의 각 상품에 대해 쿠폰을 적용해서 할인 금액 계산하는 로직
		// 쿠폰의 적용 조건 등을 확인하는 코드
		// 정책에 따라 복잡한 if-else와 계산 코드
		...
	}
	
	private Money calculateDiscount(MemberGrade grade){
		...// 등급에 따라 할인 금액 계산

	}
}

할인 정책은 주문 애그리거트가 갖고 있는 구성요소와는 관련이 없음에도 불구하고 결제 금액 계산 책임이 주문 애그리거트에 있다는 이유로 주문 애그리거트의 코드를 수정해야 한다.

이렇게 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 구현하면 안 된다. 억지로 구현하면 애그리거트는 자신의 책임 범위를 넘어서는 기능을 구현하기 때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드를 복잡하게 만들어 수정을 어렵게 만드는 요인이 된다.

7.2 도메인 서비스

도메인 서비스는 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다.

7.2.1 계산 로직과 도메인 서비스

한 애그리거트에 넣기 애매한 도메인 개념을 구현하려면 애그리거트에 억지로 넣기보다는 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러내면 된다.

도메인 영역의 애그리거트나 밸류와 같은 구성요소와 도메인 서비스를 비교할 때 다른점은 도메인 서비스는 상태 없이 로직만 구현한다는 점이다.

public class DiscountCalculationService {

	public Money calculateDiscountAmounts(
					List<OrderLine> orderLines,
					List<Coupon> coupons,
					MemberGrade grade) {

		Money couponDiscount = 
				coupons.stream()
							.map(coupon -> calculateDiscount(coupon))
							.reduce(Money(0), (v1, v2) -> v1.add(v2));

		Money membershipDiscount =
				calculateDiscount(orderer.getMember().getGrade());

		return couponDiscount.add(membershipDiscount);
	}

	private Money calculateDiscount(Coupon coupon){
		...
	}
	
	private Money calculateDiscount(MemberGrade grade){
		...
	}
}

도메인 서비스를 사용하는 주체는 애그리거트가 될 수도 있고 응용 서비스가 될 수도 있다.