학습일지/DDD

[DDD] 도메인 서비스

Merge Log 2025. 9. 21. 17:37

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

  • 만약 상품 → 주문 → 할인 쿠폰 (회원 등급에 따른 할인) 흐름으로 주문이 처리된다고 가정해보자
  • 생각해볼 수 있는 방법은 주문 애그리거트가 필요한 데이터를 모두 가지도록 한 뒤 할인 금액 계산 책임을 주문 애그리거트에 할당하는 것 이다
  • 여기서 결제 금액 계산 로직, 할인 로직 등은 주문 애그리거트의 책임이 맞는걸까?
    • 할인 정책이 변경되면 주문 애그리거트가 가지고 있는 구성 요소와는 관련이 없음에도 불구하고 책임이 주문 애그리거트에 있다는 이유로 주문 애그리거트 코드를 수정해야 한다
  • 이렇게 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 특정 애그리거트에 구현하면 안된다
  • 억지로 구현하면 애그리거트는 자신의 책임 범위를 넘어서는 기능을 구현하기 때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드 복잡성이 높아 수정하기 어려워 진다
  • 이런 문제를 해소하는 방법이 도메인 기능을 별도 서비스로 구현하는 것 이다

도메인 서비스

  • 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다
    • 계산 로직 : 여러 애그리거트가 필요한 계산 로직이나 한 애그리거트에 넣기에는 다소 복잡한 계산 로직
    • 외부 시스템 연동이 필요한 로직 : 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직
  • 애그리거트에 억지로 넣기보다 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러내면 된다
  • 도메인과의 차이점은 도메인 서비스는 상태 없이 로직만 구현한다는 점이다
public class DiscountCalculationService {
    public Money CalculateDiscountAmounts(
        ListorderLine〉 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) {...}
}
  • 할인 계산 서비스를 사용하는 주체는 애그리거트가 될 수도 있고 응용 서비스가 될 수도 있다
    • 애그리거트가 사용한다면 도메인 서비스를 애그리거트에게 전달하는 책임은 응용 서비스의 책임이다
  • 애그리거트에서 도메인 서비스를 사용한다고 해서 의존성 주입으로 처리해버리면 안된다
    • 모든 기능에서 도메인 서비스를 필요로 하는 것도 아니며 일부 기능에서만 필요로 한다
    • 일부 기능을 위해서 도메인에 해당 서비스를 의존 주입할 필요는 없다
  • 애그리거트 메서드를 실행할 때 도메인 서비스를 인자로 전달하지 않고 반대로 도메인 서비스의 기능을 실행할 때 애그리거트를 전달하기도 한다
    • 도메인 서비스는 도메인 로직을 수행하지 응용 로직을 수행하진 않는다
    • 트랜잭션 처리와 같은 로직은 응용 로직이므로 도메인 서비스가 아닌 응용 서비스에서 처리해야 한다

도메인 서비스의 위치

  • 도메인 서비스는 도메인 로직을 표현하므로 도메인 서비스의 위치는 다른 도메인 구성요소와 동일한 패키지에 위치한다

  • 도메인 서비스의 로직이 고정되어 있지 않은 경우 도메인 서비스 자체를 인터페이스로 구현하고 이를 구현한 클래스를 둘 수도 있다
  • 특히 도메인 로직을 외부 시스템이나 별도 엔진을 이용해서 구현할 때 인터페이스와 클래스를 분리하게 된다
  • 도메인 서비스의 구현이 특정 기술에 의존하거나 외부 시스템의 API 를 실행한다면 도메인 영역의 도메인 서비스는 인터페이스로 추상화해야 한다
  • 이를 통해 도메인 영역이 특정 구현에 종속되는 것을 방지할 수 있고 도메인 영역에 대한 테스트가 쉬워진다