7장: 도메인 서비스

도메인 서비스

필요성

  • 도메인 영역의 코드를 작성할 때 한 애그리거트로 기능을 구현할 수 없다면 도메인 기능을 별도 서비스로 구현해야 한다.

  • 대표적인 예로 결제 금액을 계산할 때 구매하려는 상품의 가격 정보와 주문한 상품의 개수, 적용한 할인 쿠폰의 내용, 회원의 등급 정보 등을 종합해 계산해야 한다. 이 때 결제 금액을 계산할 애그리거트를 정할 수가 없으므로 도메인 서비스를 사용해야 한다.

  • 혹은 로직 구현을 위해 외부 시스템 연동이 필요한 경우에도 도메인 서비스를 사용해야 한다.

특징

  • 도메인 서비스는 도메인 영역의 애그리거트나 밸류와 달리 상태가 없이 로직만 구현한다.

  • 아래는 할인된 가격을 계산하는 도메인 서비스의 로직으로 주문의 애그리거트 혹은 응용 서비스에서 사용될 수 있다.

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);
	}
	// ...
}
  • 계좌 이체의 기능을 수행할 때에도 도메인 서비스를 사용할 수 있다. 응용 서비스는 두 Account 애그리거트를 구한 뒤 TransferService 도메인 서비스를 이용해서 계좌 이체 도메인 기능을 실행할 것이다.

  • 도메인 서비스는 응용 영역이 아니므로 트랜잭션 처리 같은 로직은 응용 서비스에서 처리해야 한다.

public class TransferService {
	public void transfer(Account fromAcc, Account toAcc, Money amounts) {
		fromAcc.withdraw(amounts);
		toAcc.credit(amounts);
	}
	// ...

인터페이스와 클래스 분리

  • 도메인 서비스의 구현이 특정 구현 기술에 의존하거나 외부 시스템의 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();
		}
	// ...
}

패키지 위치

  • 도메인 서비스는 도메인 로직을 표현하므로 도메인 영역 패키지에 위치한다.

  • 예를 들어 주문 금액 계산을 위한 도메인 서비스는 주문 애그리거트와 동일한 패키지에 위치하도록 한다.

  • 원한다면 domain 패키지 밑에 domain.model, domain.service, domain.repository와 같이 하위 패키지를 구분하여 위치시켜도 된다.

Last updated