도메인 주도 개발 시작하기 책 정리
여러 애그리거트가 필요한 기능
✅ 어떤 애그리거트에 로직을 추가해야 할까?
도메인 영역의 코드를 작성하다 보면, 한 애그리거트로 기능을 구현할 수 없을 때가 있다.
ex) 결제 금액 계산 로직에 필요한 애그리거트
- 상품 애그리거트: 상품의 가격
- 주문 애그리거트: 상품 별 구매 개수
- 할인 쿠폰 애그리거트: 쿠폰별로 지정한 할인 금액
- 회원 애그리거트: 회원 등급에 따른 추가 할인
-> 이런 경우 억지로 특정 애그리거트에 구현하게 되면 애그리거트를 복잡하게 만든다.
도메인 서비스
✅ 도메인 서비스가 쓰이는 상황
- 계산 로직: 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직
- 외부 시스템 연동이 필요한 도메인 로직: 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직
계산 로직과 도메인 서비스
✅ 도메인 서비스 vs 애그리거트
큰 차이점은, 도메인 서비스는 애그리거트와 달리 "상태 없이 로직만 구현한다는 점"이다.
-> 즉, 도메인 서비스는 어떤 데이터를 저장하지 않고, 호출될 때마다 필요한 정보를 받아 비즈니스 로직을 수행한다는 것이다.
ex) 할인 금액 계산 로직을 위한 도메인 서비스
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(grade);
return couponDiscount.add(membershipDiscount);
}
private Money calculateDiscount(Coupon coupon) {
...
}
private Money calculateDiscount(MemberGrade grade) {
...
}
}
✅ 도메인 서비스를 사용하는 주체
애그리거트가 될 수 있고, 응용서비스가 될 수도 있다.
ex) 애그리거트가 도메인 서비스를 사용
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 OrderService {
private DiscountCalculationService discountCalculationService;
@Transactional
public OrderNo placeOrder(OrderRequest orderRequest) {
OrderNo orderNo = orderRepository.nextId();
Order order = createOrder(orderNo, orderRequest);
orderRepository.save(order);
return orderNo;
}
private Order createOrder(OrderNo orderNo, OrderRequest orderReq) {
Member member = findMember(orderReq.getOrdererId());
Order order = new Order(
orderNo,
orderReq.getOrderLines(),
orderReq.getCoupons(),
createOrderer(member),
orderReq.getShippingInfo()
);
order.calculateAmounts(this.discountCalculationService, member.getGrade());
return order;
}
}
✅ 도메인 서비스를 애그리거트에 주입하지 말자
애그리거트 메서드의 파라미터로 도메인 서비스 객체를 전달하는 것은,
애그리거트가 도메인 서비스에 의존한다는 것을 의미한다.
이런 관점으로 애그리거트에 도메인 서비스를 스프링 DI를 통해 주입받을 수 있다.
public class Order {
@Autowired
private DiscountCalculateService discountCalculateService;
...
}
그러나 도메인 객체는 필드와 메서드를 이용해 하나의 개념적인 모델을 표현한다.
여기서 discountCalculateSerivce는 데이터 자체와는 관련이 없고, DB에 저장 대상도 아니다.
또한, Order가 제공하는 모든 기능에서 discountCalculateService를 쓰는 것도 아니기에,
일부 기능을 위해 굳이 도메인 서비스 객체를 애그리거트에 의존 주입할 이유는 없다.
✅ 도메인 서비스에 애그리거트를 전달
애그리거트 메서드를 실행할 때, 도메인 서비스를 인자로 전달하는 방법이 있는 반면,
도메인 서비스를 실행할 때, 애그리거트를 전달하기도 한다.
ex) 계좌 이체
public class TransferService {
public void transfer(Account fromAcc, Account toAcc, Money amounts) {
fromAcc.withdraw(amounts);
toAcc.credit(amounts);
}
}
✅ 응용 서비스 vs 도메인 서비스
특정 기능이 응용 서비스인지 도메인 서비스인지 감을 잡기 어렵다면,
해당 로직이 애그리거트의 상태를 변경하거나, 애그리거트의 상태 값을 계산한다면 도메인 서비스라고 할 수 있다.
외부 시스템 연동과 도메인 서비스
✅ 설문조사 시스템과 사용자 역할 관리 시스템이 분리
ex) 설문 조사 시스템은 설문 조사를 생성할 때, 사용자가 권한을 가졌는지 확인해야 한다.
시스템이 분리되어 있어서, 외부 API를 호출해야 할 수도 있지만,
설문 조사 도메인 입장에서는 사용자가 권한을 가졌는지 확인하는 도메인 로직으로 볼 수 있다.
public interface SurveyPermissionChecker {
boolean hasUserCreationPermission(String userId);
}
public class CreateSurveyService {
private SurveyPermissionChecker permissionChecker;
public Long createSurvey(CreateSurveyRequest req) {
validate(req);
if (!permissionChecker.hasUserCreationPermission(req.getRequestorId())) {
throw new NoPermissionException();
}
...
}
}
여기서 중요한 점은 도메인 로직 관점에서 인터페이스 작성한 것이다.
-> 역할 관리 시스템과 연동한다는 관점으로 인터페이스를 작성하지 않았다.
-> 인터페이스를 구현한 클래스는 인프라스트럭처 영역에 위치해 연동을 포함한 권한 검사 기능을 구현한다.
도메인 서비스의 패키지 위치
✅ 애그리거트와 같은 패키지
ex) 주문 금액 계산 서비스는 이를 사용하는 주문 애그리거트와 같은 패키지에 위치
- order.domain.Order
- order.domain.DiscountCalculationService
✅ 명시적으로 구분
도메인 서비스 개수가 많아서 엔티티 밸류와 명시적으로 구분하고 싶다면 하위 패키지를 구성해도 된다.
- order.domain.model.Order
- order.domain.service.DiscountCalculationService
도메인 서비스의 인터페이스와 클래스
✅ 도메인 서비스의 로직이 고정되어 있지 않은 경우
도메인 서비스의 구현이 특정 기술에 의존하거나 외부 API를 실행한다면,
도메인 서비스는 도메인 영역에서 인터페이스로 추상화하고,
인프라스트럭처 영역에 구현체를 둘 수 있다.
'book > 도메인 주도 개발 시작하기' 카테고리의 다른 글
[DDD Start] 도메인 모델과 바운디드 컨텍스트 (0) | 2023.09.02 |
---|---|
[DDD Start] 애그리거트 트랜잭션 관리 (0) | 2023.09.02 |
[DDD Start] 응용 서비스와 표현 영역 (0) | 2023.08.12 |
[DDD Start] 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2023.08.12 |
[DDD Start] 리포지터리와 모델 구현 (0) | 2023.07.28 |