|

도메인 서비스는 언제 필요할까: 엔티티 메서드에 넣기 애매한 로직을 다루는 기준

도메인 서비스는 언제 필요할까 대표 이미지
도메인 서비스는 엔티티 안에 넣기 어색한 규칙을 도메인 언어로 드러내는 데 의미가 있다

도메인 서비스는 언제 필요한가는 DDD를 공부할수록 더 헷갈리는 주제입니다. 엔티티에 로직을 넣으라고 배웠는데, 어떤 로직은 한 엔티티 안에 넣기 어색하고, 그렇다고 애플리케이션 서비스에 두기에는 도메인 규칙처럼 느껴질 때가 있기 때문입니다.

이번 글에서는 도메인 서비스를 만능 클래스처럼 보지 않고, 엔티티 하나에 자연스럽게 귀속되지 않지만 분명히 도메인 규칙인 로직을 표현하는 도구로 설명하겠습니다. 특히 Application Service와 섞어 보는 오해도 함께 정리하겠습니다.

도메인 서비스 핵심 카드
핵심 판단 기준을 먼저 잡는 요약 카드

개념 참고로는 Martin Fowler의 Domain Service 메모를 볼 수 있습니다. 하지만 실제 설계에서는 정의보다 책임 배치가 더 중요합니다.

엔티티에 다 넣으면 왜 안 될까

엔티티는 자기 상태와 강하게 연결된 규칙을 담기에 좋습니다. 하지만 어떤 규칙은 여러 엔티티를 함께 봐야 하거나, 한 객체의 소유라고 보기 어려운 경우가 있습니다. 이런 규칙을 억지로 한 엔티티 안에 넣으면 오히려 책임이 부자연스러워집니다.

예를 들어 할인 가능 여부를 판단하려면 주문, 고객 등급, 쿠폰 정책을 함께 봐야 할 수 있습니다. 이때 특정 엔티티 하나만 주인처럼 보이지 않는다면 도메인 서비스 후보가 됩니다.


애플리케이션 서비스와는 무엇이 다를까

애플리케이션 서비스는 보통 유스케이스 흐름을 조정합니다. 트랜잭션을 열고, 리포지토리에서 객체를 가져오고, 필요한 도메인 규칙을 실행하고, 결과를 저장하는 식입니다.

반면 도메인 서비스는 흐름 조정보다 규칙 자체에 더 가깝습니다. 즉 “무엇을 어떤 순서로 호출할까”보다 “이 비즈니스 규칙은 어떻게 판단할까”에 집중합니다.

Application Service와 Domain Service 비교 카드
두 서비스가 맡는 책임을 비교한 카드

도메인 서비스가 자연스러운 예시

예를 들어 환불 가능 금액 계산, 복수 계좌 간 이체 가능 여부 판단, 배송 정책 조합 같은 규칙은 여러 객체의 상태를 함께 봐야 할 수 있습니다. 이런 경우 엔티티 한쪽에만 두면 주어가 어색해집니다.

핵심은 서비스라는 이름이 아니라, 이 로직이 도메인 언어로 읽히는지입니다. “이 유스케이스를 진행한다”가 아니라 “이 규칙을 판단한다”에 더 가까우면 도메인 서비스일 가능성이 높습니다.



실전 예시로 보면 더 분명해진다

예를 들어 주문 할인 계산을 생각해보겠습니다. 주문 금액만 보면 되는 것이 아니라, 고객 등급, 쿠폰 정책, 기간 한정 행사 조건을 함께 봐야 할 수 있습니다. 이때 특정 엔티티 하나가 모든 규칙의 자연스러운 주어라고 보기 어렵다면 도메인 서비스가 더 잘 맞습니다.

반대로 주문이 이미 출고되었는지, 취소 가능한 시점인지처럼 주문 자신의 상태만으로 충분히 판단할 수 있는 규칙은 엔티티 메서드에 두는 편이 더 자연스럽습니다. 실전에서는 “여러 객체를 본다”보다 “누가 이 규칙의 주어인가”가 더 중요한 질문입니다.

주문 할인 계산 기준 도메인 서비스 판단 예시 카드
주문 할인 계산처럼 여러 상태를 함께 보는 규칙은 도메인 서비스 후보가 될 수 있다
엔티티 메서드 도메인 서비스 애플리케이션 서비스 경계 비교 카드
세 층의 책임 경계를 한 번에 비교한 카드

하지만 너무 쉽게 서비스로 빼면 왜 위험할까

이 주제에서 가장 어려운 부분은 도메인 서비스가 필요한 경우를 찾는 것보다, 필요하지 않은데 서비스로 빼는 습관을 막는 일입니다. 서비스가 편해 보인다는 이유로 엔티티의 자기 책임까지 전부 옮기면 설계가 빠르게 빈혈화됩니다.

도메인 서비스는 편리하지만, 애매한 로직을 전부 서비스로 빼기 시작하면 다시 빈혈 모델로 돌아가기 쉽습니다. 엔티티가 가져야 할 자기 상태 관련 규칙까지 다 서비스에 모이면, 객체는 데이터 컨테이너로만 남고 규칙은 밖으로 새어 나갑니다.

즉 도메인 서비스는 엔티티를 대신하는 기본값이 아니라, 엔티티에 두기 부자연스러운 규칙을 담는 예외적 선택에 가깝습니다.

이 감각은 트랜잭션 경계 글, 책임과 객체 역할 감각은 객체지향 글과도 이어집니다. 기본 개념 참고로는 Martin Fowler의 Domain Service 메모도 볼 수 있습니다.


잘못된 설계의 대표 예시는 애매한 로직을 전부 Service 클래스로 빼는 것입니다. 이렇게 되면 엔티티는 값만 담고, 규칙은 서비스 계층에 흩어져서 결국 도메인 모델의 응집력이 무너집니다. 반대로 모든 것을 엔티티에만 밀어 넣으면 한 객체가 지나치게 무거워질 수 있습니다.

헷갈리는 포인트

  1. 서비스라는 이름이 붙었다고 다 같은 층이 아니다
  2. 흐름 조정은 애플리케이션 서비스, 규칙 표현은 도메인 서비스 쪽에 가깝다
  3. 여러 엔티티를 본다고 해서 무조건 도메인 서비스는 아니다
  4. 엔티티에 자연스럽게 둘 수 있는 규칙까지 서비스로 빼면 빈혈 모델 위험이 커진다

결국 중요한 것은 “이 로직의 주어가 누구인가”입니다. 특정 엔티티가 자기 상태를 바탕으로 자연스럽게 판단할 수 있다면 엔티티 메서드가 더 나을 수 있습니다. 반대로 주어가 규칙 그 자체라면 도메인 서비스가 더 자연스러울 수 있습니다.


도메인 서비스 판단 흐름 도식
이 로직이 도메인 서비스 후보인지 단계적으로 판단하는 흐름
도메인 서비스 남용 비교 카드
자연스러운 사용과 남용을 비교한 카드

마무리

도메인 서비스의 핵심은 서비스 클래스를 늘리는 것이 아니라, 엔티티 하나에 귀속시키기 어색한 도메인 규칙을 더 자연스럽게 표현하는 데 있습니다.

흐름 조정보다 도메인 규칙 자체를 설명하는가, 그리고 그 규칙의 주어가 특정 엔티티가 아닌가를 먼저 보면 도메인 서비스의 필요성이 훨씬 선명해집니다.

함께보면 좋은 글