Hyun's Wonderwall

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 7. 도메인 서비스 본문

Study/Java, Spring

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 7. 도메인 서비스

Hyun_! 2024. 4. 1. 18:57

EFUB 4기 BackEnd Lead_ 도메인 주도 개발 스터디

  • 스터디 커리큘럼: 최범균, "도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지"
  • 3주차 과제: Chapter 7. 도메인 서비스, Chapter 8. 애그리거트 트랜잭션 관리

Chapter 7. 도메인 서비스

  • Keywords: 응용 서비스 구현, 표현 영역의 역할, 값 검증과 권한 검사

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

결제 금액 계산 로직의 경우 고려할 사항이 많아 한 애그리거트에 넣기 애매하다.

애그리거트에서 자신의 책임 범위를 넘어서는 기능을 구현하게 하면 안 된다.

이를 해결하는 방법: 도메인 기능을 별도 서비스로 구현

7.2 도메인 서비스

도메인 서비스: 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다.

- 도메인 서비스를 사용하는 상황:

* 계산 로직: 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직

* 외부 시스템 연동이 필요한 도메인 로직: 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직

7.2.1 계산 로직과 도메인 서비스

도메인 서비스는 상태 없이 도메인 로직만 구현한다.

- 도메인 영역의 타 구성요소와 도메인 서비스를 비교할 때 다른 점이다.

- 도메인 서비스: 도메인 로직 구현 / 응용 서비스: 응용 로직 구현

한 애그리거트에 넣기 애매한 도메인 개념을 구현할 때 도메인 서비스를 이용해 도메인 개념을 명시적으로 드러낸다.

- ex. 할인 금액 계산 DiscountCalculationService

 

할인 계산 서비스를 사용하는 주체는 애그리거트가 될 수도 있고 응용 서비스가 될 수도 있다.

- 애그리거트에서 사용한다고 할 때, 애그리거트 객체에 도메인 서비스를 전달하는 것은 응용 서비스 책임이다.

주의! 도메인 서비스 객체를 애그리거트에 주입하지 않기.
애그리거트에서 도메인 서비스를 쓴다고 해서 필드로 주입하면 안 된다. 모델의 데이터를 담는 필드는 모델의 중요 구성요소인데, 계산 서비스와 같은 객체는 모델 데이터에 관련이 없고 DB 저장 대상도 아니다. 메서드 parameter로 받도록 하자.

 

- 다른 방법: 도메인 서비스의 기능을 실행할 때 애그리거트를 전달

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

응용 서비스는 두 Account 애그리거트를 구한 뒤 해당 도메인 영역의 TransferService를 이용해서 계좌 이체 도메인 기능을 실행할 것이다.

- 도메인 서비스는 도메인 로직을 수행하지 응용 로직을 수행하진 않는다.

- 트랜잭션 처리와 같은 로직은 응용 로직이므로 도메인 서비스가 아닌 응용 서비스에서 처리해야 한다.

특정 기능이 응용 서비스인지 도메인 서비스인지 파악하는 방법:
해당 로직이 애그리거트의 상태를 변경하거나 상태 값을 계산하는지 검사해 보면 된다.
예시1) 계좌 이체 로직은 계좌 애그리거트의 상태를 변경한다.
예시2) 결제 금액 로직은 주문 애그리거트의 주문 금액을 계산한다.
이 두 로직은 애그리거트를 변경하고 값을 계산하는 도메인 로직, 한 애그리거트에 넣기 적합x
=> 도메인 서비스로 구현!

7.2.2 외부 시스템 연동과 도메인 서비스

외부 시스템이나 타 도메인과의 연동 기능도 도메인 서비스가 될 수 있다.

예시 - 설문 조사를 생성할 때 사용자가 생성 권한을 가진 역할인지 확인해야 한다. 역할 관리 시스템과 연동해야 함.

1) 설문 조사 도메인 입장에서 이는 "사용자가 생성 권한을 가진 역할인지 확인하는 도메인 로직"이다. 

2) 도메인 로직 관점에서 SurveyPermissionChecker 인터페이스를 작성, boolean hasUserCreationPermission(String userId) 메서드 선언.

3) 응용 서비스 CreateSurveyService 클래스에서 이 도메인 서비스를 이용해 외부 시스템 연동을 표현한다. (SurveyPermissionChecker 객체를 필드로 주입받고 메서드 호출. 이 인터페이스를 구현한 클래스는 인프라스트럭처 영역에 위치해 연동을 포함한 권한 검사 기능을 구현한다.)

7.2.3 도메인 서비스의 패키지 위치

도메인 서비스의 위치는 다른 도메인 구성요소와 동일한 패키지에 위치한다.

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

- 응용: application, 도메인: domain

- domain.model, domain.service, domain.repository로 구분해도 된다.

7.2.4 도메인 서비스의 인터페이스와 클래스

도메인 서비스의 로직이 고정되어있지 않은 경우 도메인 서비스 자체를 인터페이스로 구현하고 이를 구현한 클래스를 둘 수도 있다.

특히 도메인 로직을 외부 시스템이나 별도 엔진을 이용해서 구현할 때 인터페이스와 클래스를 분리하게 된다.

도메인 서비스의 구현이 특정 기술에 의존하거나 외부 시스템의 API를 실행한다면 도메인 영역의 도메인 서비스는 인터페이스로 추상화해야 한다. 이를 통해 도메인 영역이 특정 구현에 종속되는 것을 방지할 수 있고 도메인 영역에 대한 테스트가 쉬워진다.