본문 바로가기

DDD/도메인 주도 설계 철저 입문

엔티티 중복 확인은 도메인 서비스인가 앱 서비스인가 - 「도메인 주도 설계 철저 입문」 11장 (3)

 

 

저자의 주장 :

엔티티 중복 확인은 도메인 서비스가 해야 한다. 책에서는 엔티티 중복 확인 서비스를 다음과 같이 도메인 서비스로 구현하였다. 저자가 이렇게 한 이유는 "이름을 기준으로 중복을 판단한다"와 같은 규칙이 도메인 지식이기 때문이다. 저자는 도메인 지식은 한 곳에 모여 있어야 한다고 주장한다. 도메인에 관한 지식이 도메인 서비스에 모두 모여 있으면 유지 보수 측면에서 도메인 서비스 클래스만 확인하면 되므로 효율적이라고 볼 수 있다. 

public class CircleService {

    private final CircleRepository circleRepository;

    public CircleService(CircleRepository circleRepository) {
        this.circleRepository = circleRepository;
    }

    public boolean exists(Circle circle) {
        Circle duplicated = circleRepository.find(circle.getName());
        return duplicated != null;
    }
}

 

 

나의 견해 :

그러나 나의 의견은 다르다. 엔티티 중복 확인은 앱 서비스가 해야 한다. 엔티티 중복 확인을 위해서 도메인 서비스가 리포지토리에 직접 의존하게 되면 도메인이 더 이상 순수한 도메인으로 남기 어렵기 때문이다. 도메인은 외부 시스템에 대한 의존성 없이 비즈니스 로직만을 담아야 한다. 그리고 앱 서비스와 도메인 서비스의 관계를 생각해보면 도메인 서비스가 infrastructure에 해당하는 리포지토리에 의존하는 것이 더욱더 이상하다는 것을 알 수 있다. 

 

 

도메인 서비스가 리포지토리에 의존하는 구조

왼쪽은 앱 서비스만이 리포지토리를 사용함으로써 도메인의 순수성을 지키는 상태이고, 오른쪽은 앱서비스와 도메인 서비스 둘 다 리포지토리에 의존하는 구조이다. 중복 검사 메소드`exist`는 리포지토리의 `find` 메소드를 사용해서 엔티티가 이미 존재하는지 확인한다. 좌측 그림에서는 `exist`가 `AppService`에 구현되어 있고, 따라서 `AppService`만 `Resporitory`에 의존한다. 

 

반면 우측 그림에서는 `exist`가 `DomainService`에 구현되어 있기 때문에 `DomainService`가 `Repository`에 의존한다. `AppService`는 다양한 유스케이스에서 리포지토리의 메소드를 자주 사용할 수밖에 없기 때문에 `AppService`는 기본적으로 `Repository`에 의존한다. 

 

DDD 철학에 걸맞는 구조는 왼쪽

 

 

 

DDD에서 도메인 서비스와 앱 서비스 차이점 비교 분석에서도 밝혔듯이, 인프라에 직접적으로 의존하는 것은 앱 서비스의 몫으로 남겨두는 편이 바람직하다고 생각한다. 3-Layerd 아키텍처와 비교했을 때 DDD는 어디까지나 도메인을 여타 다른 작업으로부터 분리하는 것을 지향하기 때문이다.

 

 

앱 서비스에 중복 검사 기능 구현하기

따라서 앱 서비스에 중복 검사 `exist`를 구현한 코드는 다음과 같다. 

public class CircleApplicationService {
    private final ICircleRepository circleRepository;
    private final ICircleFactory circleFactory;
    private final UserApplicationService userApplicationService;

    public CircleApplicationService(ICircleRepository circleRepository, ICircleFactory circleFactory, IUserRepository userRepository, UserApplicationService userApplicationService) {
		// 생략
    }

    public void createCircle(CircleCreateCommand createCommand) {
        // 생략
    }
    
    public void joinCircle(CircleJoinCommand joinCommand) {
        // 생략
    }
    
    // Entity가 이미 Repository에 존재하는지 확인하는 기능은
    // 도메인 서비스일까 아니면 앱 서비스일까?
    // 도메인 주도 설계 철처 입문 저자는 도메인 서비스라고 하지만,
    // 도메인 서비스가 Repository에 직접 의존하지 않아야
    // 순수한 도메인일 수 있을 것 같아서
    // 앱 서비스로 구현한다.
    public boolean circleExists(CircleName circleName) {
        return circleRepository.findByNameOrNull(circleName) != null;
    }
}

 

 

 

깃헙 프로젝트 참고(🔗)