응용 프로그램 또는 도메인 서비스의 DDD 리포지토리


29

요즘 DDD를 공부하고 있는데 DDD로 리포지토리를 관리하는 방법에 대한 몇 가지 질문이 있습니다.

실제로, 나는 두 가지 가능성을 만났다.

첫 번째

내가 읽은 서비스를 관리하는 첫 번째 방법은 응용 프로그램 서비스에 리포지토리와 도메인 모델을 주입하는 것입니다.

이러한 방식으로 응용 프로그램 서비스 방법 중 하나에서 도메인 서비스 방법 (비즈니스 규칙 확인)을 호출하고 조건이 양호하면 저장소를 데이터베이스에서 엔티티를 유지 / 검색하기 위해 특수한 방법으로 호출합니다.

이를 수행하는 간단한 방법은 다음과 같습니다.

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

두번째 것

두 번째 가능성은 대신 domainService 내부에 저장소를 삽입하고 도메인 서비스를 통해서만 저장소를 사용하는 것입니다.

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

지금부터 나는 어느 것이 가장 좋은지 (하나가 가장 좋은 것이 있는지) 또는 그들이 맥락에서 두 가지를 의미하는 것을 구별 할 수 없습니다.

어느 쪽이 다른 쪽보다 낫고 왜 그럴 수 있는지 예를 들어 주시겠습니까?



"응용 프로그램 서비스에 리포지토리 및 도메인 모델을 주입합니다." 어딘가에 "도메인 모델"을 주입한다는 것은 무슨 뜻입니까? DDD 도메인 모델 측면에서 AFAICT는 도메인의 전체 개념 세트와 애플리케이션과 관련된 개념 간의 상호 작용을 의미합니다. 그것은 추상적 인 것입니다. 메모리 내 객체가 아닙니다. 주사 할 수 없습니다.
Alexey

답변:


31

짧은 대답은 응용 프로그램 서비스 또는 도메인 서비스에서 리포지토리를 사용할 수 있다는 것입니다. 그러나 그 이유와 방법을 고려하는 것이 중요합니다.

도메인 서비스의 목적

도메인 서비스는 도메인 개념 / 논리를 캡슐화해야합니다. 도메인 서비스 방법은 다음과 같습니다.

domainService.persist(data)

유비쿼터스 언어persist 의 일부가 아니기 때문에 도메인 서비스에 속하지 않으며 지속성 조작은 도메인 비즈니스 로직의 일부가 아닙니다.

일반적으로 도메인 서비스는 둘 이상의 집계를 조정하거나 작업해야하는 비즈니스 규칙 / 논리가있는 경우 유용합니다. 논리에 하나의 집계 만 포함 된 경우 해당 집계의 엔티티에 대한 메소드에 있어야합니다.

응용 프로그램 서비스의 리포지토리

따라서 그러한 의미에서 귀하의 예에서는 첫 번째 옵션을 선호하지만 도메인 서비스가 API의 원시 데이터를 허용하므로 개선해야 할 여지가 있습니다. 왜 도메인 서비스가 data? 의 구조에 대해 알아야 합니까? 또한 데이터는 단일 집계에만 관련된 것으로 보이므로 도메인 서비스 사용에 대한 가치는 제한적입니다. 일반적으로 엔터티 생성자 안에 유효성 검사를 넣습니다. 예 :

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

유효하지 않은 경우 예외를 던집니다. 애플리케이션 프레임 워크에 따라 예외를 포착하고 API 유형에 대한 적절한 응답 (예 : REST API의 경우)에 대해 400 상태 코드를 리턴하는 일관된 메커니즘을 갖는 것이 간단 할 수 있습니다.

도메인 서비스의 리포지토리

위의 내용에도 불구하고 도메인 서비스에 리포지토리를 주입하여 사용하는 것이 유용하지만 리포지토리가 구현되어 집계 루트 만 허용하고 반환 할뿐 아니라 여러 집계를 포함하는 논리를 추상화하는 경우에만 유용합니다. 예 :

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

도메인 서비스의 구현은 다음과 같습니다.

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

결론

여기서 핵심은 도메인 서비스 가 유비쿼터스 언어의 일부인 프로세스를 캡슐화 한다는 것입니다. 역할을 수행하려면 리포지토리를 사용해야하며 그렇게하는 것이 좋습니다.

그러나 리포지토리를 래핑하는 도메인 서비스를 persist추가 하는 방법은 가치가 거의 없습니다.

이를 바탕으로 응용 프로그램 서비스가 단일 집계 작업 만 요구하는 사용 사례를 표현하는 경우 응용 프로그램 서비스에서 직접 리포지토리를 사용하는 데 아무런 문제가 없습니다.


자, 비즈니스 규칙 (사양 패턴 규칙 지정)이 하나의 엔티티에만 관련된 경우 해당 엔티티의 유효성 검사 만 수행해야합니까? 사용자 엔터티 내부에서 올바른 사용자 메일 형식을 제어하는 ​​것과 같은 비즈니스 규칙을 삽입하는 것이 이상하게 보입니다. 그렇지 않습니까? 글로벌 응답에 대해서는 감사합니다. "적용 할 기본 규칙"이 없으며 사용 사례에 따라 다릅니다. 나는이 모든 일을 잘 구별하기 위해해야 ​​할 일이있다
mfrachet

2
명확히하기 위해 엔터티에 속하는 규칙은 해당 엔터티의 책임 인 규칙 일뿐입니다. 올바른 사용자 이메일 형식을 제어한다고해서 사용자 엔티티에 속하지 않는 것 같습니다. 개인적으로 저는 전자 메일 주소를 나타내는 Value Object에 이와 같은 유효성 검사 규칙을 적용하고 싶습니다. 사용자는 EmailAddress 유형의 속성을 가지며 EmailAddress 생성자는 문자열을 허용하고 문자열이 필요한 형식과 일치하지 않으면 예외를 발생시킵니다. 그런 다음 이메일 주소를 저장해야하는 다른 엔티티에서 EmailAddress ValueObject를 재사용 할 수 있습니다.
Chris Simon

이제 Value Object를 사용해야하는 이유를 알았습니다. 그러나 이는 가치 객체가 형식을 관리하는 비즈니스 규칙 인 속성을 가지고 있음을 의미합니다.
mfrachet

1
값 객체는 변경할 수 없어야합니다. 일반적으로 이것은 생성자에서 초기화 및 유효성 검사를 의미하며 모든 속성에 대해 공개 get / private 세트 패턴을 사용합니다. 그러나 언어 구성을 사용하여 등식, ToString 프로세스 등을 정의 할 수 있습니다. 예 : kacper.gunia.me/ddd-building-blocks-in-php-value-object 또는 github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon

@ChrisSimon에게 감사 드리고 마지막으로 이론뿐만 아니라 코드와 관련된 실제 DDD 상황에 답하십시오. 나는 총체를 생성하고 저장하는 기능적인 예를 위해 SO와 웹을 트롤링하는 데 5 일을 보냈으며 이것이 내가 찾은 가장 명확한 설명입니다.
e_i_pi

2

허용되는 답변에 문제가 있습니다.

도메인 모델은 저장소에 의존 할 수 없으며 도메인 서비스는 도메인 모델의 일부입니다-> 도메인 서비스는 저장소에 의존해서는 안됩니다.

대신 응용 프로그램 서비스에서 이미 비즈니스 논리 실행에 필요한 모든 엔터티를 조립 한 다음 모델에 인스턴스화 된 개체를 제공하면됩니다.

귀하의 예에 따르면 다음과 같이 보일 수 있습니다.

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

따라서 경험 법칙 : 도메인 모델은 외부 레이어에 의존하지 않습니다

도메인 서비스 대 응용 프로그램 에서 이 문서 :

  • 도메인 서비스는 응용 프로그램 서비스가 API를 제공 할 목적으로 사용되는 경우 매우 세분화됩니다.

  • 도메인 서비스에는 엔터티 또는 값 개체에 자연적으로 배치 할 수없는 도메인 논리가 포함되어 있지만 응용 프로그램 서비스는 도메인 논리의 실행을 조정하고 자체적으로 도메인 논리를 구현하지는 않습니다.

  • 도메인 서비스 메소드는 다른 도메인 요소를 피연산자 및 리턴 값으로 가질 수 있지만 애플리케이션 서비스는 ID 값 및 기본 데이터 구조와 같은 사소한 피연산자에서 작동합니다.

  • 응용 프로그램 서비스는 도메인 논리를 실행하는 데 필요한 인프라 서비스에 대한 종속성을 선언합니다.


1

서비스와 객체가 일관된 책임 세트를 캡슐화하지 않는 한 패턴 중 어느 것도 좋지 않습니다.

먼저 도메인 객체가 무엇인지 말하고 도메인 언어 내에서 무엇을 할 수 있는지에 대해 이야기하십시오. 유효하거나 유효하지 않은 경우 도메인 개체 자체의 속성으로 사용하지 않는 이유는 무엇입니까?

예를 들어 객체 유효성이 다른 객체의 관점에서만 의미가있는 경우 서비스 집합에 캡슐화 될 수있는 '도메인 객체에 대한 유효성 검사 규칙 X'가 있습니다.

객체를 검증하려면 비즈니스 규칙 내에 객체를 저장해야합니까? 아마 아닙니다. '객체 저장'책임은 일반적으로 별도의 저장소 객체에 있습니다.

이제 다양한 책임을 다루고, 객체를 생성하고, 유효성을 검사하고, 유효한 경우 저장하려는 작업을 수행했습니다.

이 작업이 도메인 개체에 고유합니까? 그런 다음 해당 객체의 일부로 만드십시오.ExamQuestion.Answer(string answer)

도메인의 다른 부분에 적합합니까? 거기에 넣어Basket.Purchase(Order order)

ADM REST 서비스를 원하십니까? 자 그리고 나서.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.