web-dev-qa-db-fra.com

Référentiels DDD dans le service d'application ou de domaine

J'étudie DDD ces jours-ci, et j'ai des questions sur la façon de gérer les référentiels avec DDD.

En fait, j'ai rencontré deux possibilités:

Premier

La première façon de gérer les services que j'ai lus est d'injecter un référentiel et un modèle de domaine dans un service d'application.

De cette façon, dans l'une des méthodes de service d'application, nous appelons une méthode de service de domaine (vérification des règles métier) et si la condition est bonne, le référentiel est appelé sur une méthode spéciale pour persister/récupérer l'entité de la base de données.

Une façon simple de procéder pourrait être:

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

}

Deuxième

La deuxième possibilité consiste à injecter le référentiel à l'intérieur de domainService à la place, et à utiliser uniquement le référentiel via le service de domaine:

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
  }

}

À partir de maintenant, je ne suis pas en mesure de distinguer lequel est le meilleur (s'il y en a un de mieux) ou ce qu'ils impliquent les deux dans leur contexte.

Pouvez-vous me donner un exemple où l'un pourrait être meilleur que l'autre et pourquoi?

31
mfrachet

La réponse courte est - vous pouvez utiliser des référentiels à partir d'un service d'application ou d'un service de domaine - mais il est important de considérer pourquoi et comment vous le faites.

Objectif d'un service de domaine

Les services de domaine doivent encapsuler les concepts/la logique du domaine - en tant que tel, la méthode du service de domaine:

domainService.persist(data)

n'appartient pas à un service de domaine, car persist ne fait pas partie du langage omniprésent et l'opération de persistance ne fait pas partie de la logique métier du domaine.

En règle générale, les services de domaine sont utiles lorsque vous avez des règles/logiques métier qui nécessitent de coordonner ou de travailler avec plusieurs agrégats. Si la logique n'implique qu'un seul agrégat, elle doit se trouver dans une méthode sur les entités de cet agrégat.

Référentiels dans Application Services

Donc, dans ce sens, dans votre exemple, je préfère votre première option - mais même là, il y a place à amélioration, car votre service de domaine accepte les données brutes de l'API - pourquoi le service de domaine devrait-il connaître la structure de data ?. De plus, les données ne semblent être liées qu'à un seul agrégat, il y a donc une valeur limitée à utiliser un service de domaine pour cela - en général, je mettrais la validation dans le constructeur d'entité. par exemple.

postAction(data){

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

  this.repository.persist(entity);

  // ...
}

et lever une exception si elle n'est pas valide. Selon votre cadre d'application, il peut être simple d'avoir un mécanisme cohérent pour intercepter l'exception et la mapper à la réponse appropriée pour le type d'api - par exemple pour une API REST, renvoyez un code d'état 400.

Référentiels dans les services de domaine

Nonobstant ce qui précède, il est parfois utile d'injecter et d'utiliser un référentiel dans un service de domaine, mais uniquement si vos référentiels sont implémentés de telle sorte qu'ils acceptent et renvoient uniquement des racines d'agrégat, et également lorsque vous abstrayez une logique impliquant plusieurs agrégats. par exemple.

postAction(data){

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

  // ...
}

l'implémentation du service de domaine ressemblerait à:

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

}

Conclusion

La clé ici est que le service de domaine encapsule un processus qui fait partie du langage omniprésent. Pour remplir son rôle, il doit utiliser des référentiels - et c'est parfaitement bien de le faire.

Mais l'ajout d'un service de domaine qui enveloppe un référentiel avec une méthode appelée persist ajoute peu de valeur.

Sur cette base, si votre service d'application exprime un cas d'utilisation qui appelle à ne travailler qu'avec un seul agrégat, il n'y a aucun problème à utiliser le référentiel directement à partir du service d'application.

34
Chris Simon

Il y a un problème avec la réponse acceptée:

Le modèle de domaine n'est pas autorisé à dépendre du référentiel et le service de domaine fait partie du modèle de domaine -> le service de domaine ne doit pas dépendre du référentiel.

À la place, vous devez assembler toutes vos entités nécessaires à l'exécution de la logique métier déjà dans le service d'application, puis fournir simplement à vos modèles des objets instanciés.

D'après votre exemple, cela pourrait ressembler à ceci:

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

Donc, règle générale: Le modèle de domaine ne dépend pas des couches externes

Application vs service de domaine De cet article :

  • Les services de domaine sont très granulaires alors que les services d'application sont une façade destinée à fournir une API.

  • Les services de domaine contiennent une logique de domaine qui ne peut naturellement pas être placée dans une entité ou un objet de valeur tandis que les services d'application orchestrent l'exécution de la logique de domaine et n'implémentent pas eux-mêmes de logique de domaine.

  • Les méthodes de service de domaine peuvent avoir d'autres éléments de domaine comme opérandes et valeurs de retour tandis que les services d'application fonctionnent sur des opérandes triviaux tels que les valeurs d'identité et les structures de données primitives.

  • Les services d'application déclarent les dépendances des services d'infrastructure requis pour exécuter la logique du domaine.

4
sMs

Aucun de vos modèles n'est bon à moins que vos services et objets n'encapsulent un ensemble cohérent de responsabilités.

Dites d'abord ce qu'est votre objet de domaine et parlez de ce qu'il peut faire dans le langage de domaine. S'il peut être valide ou invalide, pourquoi ne pas l'avoir comme propriété de l'objet de domaine lui-même?

Si, par exemple, la validité des objets n'a de sens qu'en termes d'un autre objet, alors vous avez peut-être une responsabilité "règle de validation X pour les objets de domaine" qui peut être encapsulée dans un ensemble de services.

La validation d'un objet nécessite-t-elle de le stocker dans vos règles métier? Probablement pas. La responsabilité du "stockage d'objets" va normalement dans un objet de référentiel séparé.

Vous avez maintenant une opération que vous souhaitez effectuer, qui couvre un éventail de responsabilités, créez un objet, validez-le et, s'il est valide, stockez-le.

Cette opération est-elle intrinsèque à l'objet domaine? Ensuite, intégrez-le à cet objet, c'est-à-dire ExamQuestion.Answer(string answer)

Cela correspond-il à une autre partie de votre domaine? mettez-le là Basket.Purchase(Order order)

Préférez-vous faire des services ADM REST?? Ok alors.

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