Repositorios DDD en servicio de aplicación o dominio

29

Actualmente estoy estudiando DDD y tengo algunas preguntas sobre cómo administrar los repositorios con DDD.

En realidad, he encontrado dos posibilidades:

El primero

La primera forma de administrar los servicios que he leído es inyectar un repositorio y un modelo de dominio en un servicio de aplicación.

De esta manera, en uno de los métodos de servicio de la aplicación, llamamos a un método de servicio de dominio (verificando las reglas de negocio) y si la condición es buena, el repositorio se llama a un método especial para persistir / recuperar la entidad de la base de datos.

Una forma simple de hacer esto podría ser:

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))
    }
    // ...
  }

}

Segundo

La segunda posibilidad es inyectar el repositorio dentro del dominioServicio, y usar el repositorio solo a través del servicio de dominio:

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
  }

}

A partir de ahora, no puedo distinguir cuál es el mejor (si hay uno mejor) o qué implican ambos en su contexto.

¿Me puede dar un ejemplo donde uno podría ser mejor que el otro y por qué?

mfrachet
fuente
"para inyectar un repositorio y un modelo de dominio en un servicio de aplicación". ¿Qué quieres decir con inyectar un "modelo de dominio" en alguna parte? AFAICT en términos de modelo de dominio DDD significa todo el conjunto de conceptos del dominio y las interacciones entre ellos que son relevantes para la aplicación. Es algo abstracto, no es un objeto en memoria. No puedes inyectarlo.
Alexey

Respuestas:

31

La respuesta breve es: puede usar repositorios de un servicio de aplicación o un servicio de dominio, pero es importante considerar por qué y cómo lo está haciendo.

Propósito de un servicio de dominio

Los Servicios de dominio deben encapsular conceptos / lógica de dominio, como tal, el método de servicio de dominio:

domainService.persist(data)

no pertenece a un servicio de dominio, ya persistque no forma parte del lenguaje omnipresente y la operación de persistencia no forma parte de la lógica empresarial del dominio.

En general, los servicios de dominio son útiles cuando tiene reglas / lógica de negocios que requieren coordinar o trabajar con más de un agregado. Si la lógica solo involucra un agregado, debe estar en un método en las entidades de ese agregado.

Repositorios en Servicios de Aplicación

Entonces, en ese sentido, en su ejemplo, prefiero su primera opción, pero incluso allí hay margen de mejora, ya que su servicio de dominio está aceptando datos sin procesar de la API, ¿por qué el servicio de dominio debe conocer la estructura de data? Además, los datos parecen estar relacionados solo con un agregado único, por lo que hay un valor limitado en el uso de un servicio de dominio para eso; en general, pondría la validación dentro del constructor de la entidad. p.ej

postAction(data){

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

  this.repository.persist(entity);

  // ...
}

y lanzar una excepción si no es válida. Dependiendo del marco de su aplicación, puede ser simple tener un mecanismo consistente para detectar la excepción y asignarla a la respuesta apropiada para el tipo de API; por ejemplo, para una API REST, devuelva un código de estado 400.

Repositorios en Servicios de Dominio

A pesar de lo anterior, a veces es útil inyectar y usar un repositorio en un servicio de dominio, pero solo si sus repositorios se implementan de modo que acepten y devuelvan solo raíces agregadas, y también cuando está abstrayendo lógica que involucra múltiples agregados. p.ej

postAction(data){

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

  // ...
}

la implementación del servicio de dominio se vería así:

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);

}

Conclusión

La clave aquí es que el servicio de dominio encapsula un proceso que es parte del lenguaje ubicuo. Para cumplir su función, necesita usar repositorios, y está perfectamente bien hacerlo.

Pero agregar un servicio de dominio que envuelve un repositorio con un método llamado persistagrega poco valor.

Sobre esa base, si el servicio de su aplicación expresa un caso de uso que solo requiere trabajar con un único agregado, no hay ningún problema al usar el repositorio directamente desde el servicio de la aplicación.

Chris Simon
fuente
De acuerdo, entonces si tengo reglas comerciales (admitiendo las reglas del Patrón de especificación), si solo concierne a una entidad, ¿debería la validación en esa entidad? Parece extraño inyectar reglas comerciales como controlar un buen formato de correo de usuario dentro de la entidad de usuario. ¿No es así? En cuanto a la respuesta global, gracias. Obtuvo que no hay una "regla predeterminada para aplicar", y realmente depende de nuestros casos de uso. Tengo mucho trabajo que hacer para distinguir bien todo este trabajo
mfrachet
2
Para aclarar, las reglas que pertenecen a la entidad son solo las reglas que son responsabilidad de esa entidad. Estoy de acuerdo, controlar un buen formato de correo electrónico de usuario no parece pertenecer a la entidad Usuario. Personalmente, me gusta poner reglas de validación como esa en un objeto de valor que representa una dirección de correo electrónico. El usuario tendría una propiedad de tipo EmailAddress, y el constructor EmailAddress acepta una cadena y lanza una excepción si la cadena no coincide con el formato requerido. Luego, puede volver a utilizar EmailAddress ValueObject en otras entidades que necesiten almacenar una dirección de correo electrónico.
Chris Simon
Bien, veo por qué usar Value Object ahora. ¿Pero significa que el objeto de valor debe una propiedad que es la regla de negocios que administra el formato?
mfrachet
1
Los objetos de valor deben ser inmutables. En general, esto significa que usted inicializa y valida en el constructor, y para cualquier propiedad, use el patrón público get / private set. Pero puede usar construcciones de lenguaje para definir la igualdad, el proceso ToString, etc., por ejemplo, kacper.gunia.me/ddd-building-blocks-in-php-value-object o github.com/spring-projects/spring-gemfire-examples/ blob / master / ...
Chris Simon
Gracias @ChrisSimon, finalmente y responde a una situación DDD de la vida real que involucra código y no solo teoría. He pasado 5 días rastreando SO y la web en busca de un ejemplo funcional de creación y guardado de un agregado, y esta es la explicación más clara que he encontrado.
e_i_pi
2

Hay un problema con la respuesta aceptada:

El modelo de dominio no puede depender del repositorio y el servicio de dominio es parte del modelo de dominio -> el servicio de dominio no debe depender del repositorio.

Lo que debe hacer en su lugar es ensamblar todas las entidades necesarias para la ejecución de la lógica de negocios que ya están en el servicio de aplicaciones y luego simplemente proporcionar a sus modelos objetos instanciados.

Según su ejemplo, podría verse así:

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))
    }
    // ...
  }
}

Entonces, regla general: el modelo de dominio no depende de las capas externas

Aplicación vs Servicio de dominio De este artículo :

  • Los servicios de dominio son muy granulares, ya que los servicios de aplicaciones son una fachada destinada a proporcionar una API.

  • Los servicios de dominio contienen lógica de dominio que no se puede colocar naturalmente en una entidad u objeto de valor, mientras que los servicios de aplicación organizan la ejecución de la lógica de dominio y no implementan ellos mismos ninguna lógica de dominio.

  • Los métodos de servicio de dominio pueden tener otros elementos de dominio como operandos y valores de retorno, mientras que los servicios de aplicación operan sobre operandos triviales como valores de identidad y estructuras de datos primitivas.

  • Los servicios de aplicación declaran dependencias de los servicios de infraestructura necesarios para ejecutar la lógica de dominio.

SMS
fuente
1

Ninguno de sus patrones es bueno a menos que sus servicios y objetos encapsulen un conjunto coherente de responsabilidad.

En primer lugar, diga cuál es su objeto de dominio y hable sobre lo que puede hacer dentro del idioma del dominio. Si puede ser válido o no válido, ¿por qué no tener esto como una propiedad del objeto de dominio en sí?

Si, por ejemplo, la validez de los objetos solo tiene sentido en términos de otro objeto, entonces tal vez tenga una 'regla de validación X para los objetos de dominio' de responsabilidad que puede encapsularse en un conjunto de servicios.

¿Validar un objeto necesita almacenarlo dentro de las reglas de su negocio? Probablemente no. La responsabilidad de "almacenar objetos" normalmente va en un objeto de repositorio separado.

Ahora tiene una operación que desea realizar que cubre un rango de responsabilidades, crear un objeto, validarlo y, si es válido, almacenarlo.

¿Es esta operación intrínseca al objeto de dominio? Luego hazlo parte de ese objeto, es decirExamQuestion.Answer(string answer)

¿Encaja con alguna otra parte de tu dominio? ponlo ahíBasket.Purchase(Order order)

¿Prefieres hacer los servicios ADM REST? OK entonces.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Ewan
fuente