Chapter 7
7.1 여러 애그리거트가 필요한 기능
도메인 영역의 코드를 작성하다 보면, 한 애그리거트로 기능을 구현할 수 없을 때가 있다.
- 상품 애그리거트: 구매하는 상품의 가격이 필요하다. 또한 상품에 따라 배송비가 추가되기도 한다.
- 주문 애그리거트: 상품별로 구매 개수가 필요하다.
- 할인 쿠폰 애그리거트: 쿠폰별로 지정한 할인 금액이나 비율에 따라 주문 총 금액을 할인한다.
- 할인 쿠폰을 조건에 따라 중복 사용할 수 있다거나 지정한 카테고리의 상품에만 적용할 수 있다는 제약조건이 있을 수있다.
- 회원 애그리거트: 회원 등급에 따라 추가 할인이 가능하다.
이 상황에서 실제 결제 금액을 계산해야하는 주체는 주문 애그리거트일까? 그러나 총 주문 금액에서 할인 금액을 계산해야하는데 이 할인 금액을 구하는 것은 어떤 도메인에서 해야할까?
일단 생각해 볼 수 있는 방법은 주문 애그리거트가 필요한 데이터를 모두 가지도록 한 뒤 할인 계산 책임을 주문 애그리거트에게 할당하는 것이다.
public class Order {
private Orderer orderer;
private Money totalAmounts;
private List<OrderLine> orderLines;
private List<Cupon> usedCupons;
private Money calculatePayAmount(){
Money discount = usedCupons.map(copon -> calculateDiscount(cupon))
.reduce(Money(0), (v1, v2) -> v1.add(v2));
Money membershipDiscount = calculateDiscount(orderer.getMember().getGrade());
return totalAmounts.minus(discount).minus(memberShipDiscount);
}
}
그러나 이렇게 구현을 해도 추가적인 기능이 생긴다면, 도메인간의 경계가 모호해질 수 있다.
예를 들어 특별 감사 세일로 전 품목에 대해 한 달간 2% 추가 할인을 하기로 했다고 해보자. 이 할인 정책은 주문 애그리거트가 각고 있는 구성요소와는 관련이 없음에도 불구하고 결제 금액 책임이 주문 애그리거트에 있다는 이유로 주문 애기르거트의 코드를 수정해야한다.
이렇게 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 특정 애그리거트에 구현하면 안된다. 억지로 구현하면 애그리거트는 자신의 책임 범위를 넘어서는 기능을 구현하기 때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드를 복잡하게 만들 수 있다.
따라서 이런 문제를 해결하려면 도메인 기능을 별도 서비스로 구현하는 것이다.
7.2 도메인 서비스
도메인 서비스는 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다. 주로 다음 상황에서 도메인 서비스를 사용한다.
- 계산 로직
- 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직
- 외부 시스템 연동이 필요한 도메인 로직
- 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직
7.2.1 계란 로직과 도메인 서비스
도메인 서비스는 상태 없이 로직만을 구현한다는 것이 가장 중요한 점이다.
public class DiscountCalculationService {
public Money calculateDiscountAmounts(
List<OrderLine> orderLines,
List<Coupon> coupons,
MemberGrade grade
){
Money couponDiscount = cupons.stream()
.map(cupon -> calculateDiscount(cupon))
.reduce(Money(0), (v1, v2) -> v1.add(v2));
Money membershipDiscount = calculateDiscount(orderer.getMember().getGrade());
return couponDiscount.add(membershipDiscount);
}
}
DiscountCalculationService를 Order 도메인에서 전달받아 사용하면 사용 주체는 Order 도메인이 된다.(?)
public class Order {
public void calculateAmounts(DiscountCalculationService disCalSvc, MemberGrade grade){
Money totalAmounts = getTotalAmounts();
Money discountAmounts = disCalSvc.calculateDiscountAmounts(this.orderLines, this.coupons, grade);
this.paymentAmounts = totalAmounts.minus(discountAmounts);
}
}
응용 서비스에서는 애그리거트 객체에게 도메인 서비스를 전달한다. (굳이 왜 전달하나 의문이긴 하다.)
public class TransferService {
public void transfer(Account fromAcc, Account toAcc, Money amounts){
fromAcc.withdraw(amounts);
toAcc.credit(amounts);
}
}
이건 응용 서비스에서 불러주는 도메인 서비스이다. 흐음... 구분이 좀 복잡한 기분이 있긴하다.
7.2.2 외부 시스템 연동과 도메인 서비스
외부 시스템이나 타 도메인과의 연동 기능도 도메인 서비스가 될 수 있다.
7.2.3 도메인 서비스의 패키지 위치
도메인 서비스는 도메인 로직을 표현하므로 도메인 서비스의 위치는 다른 도메인 구성요소와 동일한 패키지에 위치한다.
7.2.4 도메인 서비스의 인터페이스와 클래스
도메인 서비스의 로직이 고정되어 있지 않은 경우 도메인 서비스 자체를 인터페이스로 구현하고 이를 구현한 클래스를 둘 수도 있다.
'programming > DDD' 카테고리의 다른 글
[DDD] chapter 9 (1) | 2024.02.27 |
---|---|
[DDD] chapter 8 (0) | 2024.02.21 |
[DDD] chapter 6 (0) | 2024.02.10 |
[DDD] chapter 5 (1) | 2024.02.07 |
[DDD] chapter 4 (0) | 2024.02.03 |