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

29

J'étudie DDD ces jours-ci, et j'ai des questions concernant la gestion des 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 à n'utiliser le référentiel que 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) ou ce qu'ils impliquent tous les deux dans leur contexte.

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

mfrachet
la source
"pour injecter un référentiel et un modèle de domaine dans un service d'application." Que voulez-vous dire par l'injection d'un "modèle de domaine" quelque part? AFAICT en termes de modèle de domaine DDD signifie l'ensemble des concepts du domaine et les interactions entre eux qui sont pertinents pour l'application. C'est une chose abstraite, ce n'est pas un objet en mémoire. Vous ne pouvez pas l'injecter.
Alexey

Réponses:

31

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 il persistne 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 / logique 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 de l'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. En fonction de votre infrastructure 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, renvoyer 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 persistajoute 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.

Chris Simon
la source
D'accord, donc si j'ai des règles métier (en admettant des règles de modèle de spécification), si cela ne concerne qu'une seule entité, je devrais mais la validation dans cette entité? Il semble étrange d'injecter des règles métier comme le contrôle d'un bon format de courrier utilisateur à l'intérieur de l'entité utilisateur. N'est-ce pas? Concernant la réponse globale, merci. Il n'y a pas de "règle par défaut à appliquer", et cela dépend vraiment de nos cas d'utilisation. J'ai du travail à faire pour bien distinguer tout ce travail
mfrachet
2
Pour clarifier, les règles qui appartiennent à l'entité ne sont que les règles qui relèvent de cette entité. Je suis d'accord, contrôler un bon format de courrier électronique utilisateur ne semble pas appartenir à l'entité Utilisateur. Personnellement, j'aime mettre des règles de validation comme ça dans un objet de valeur qui représente une adresse e-mail. L'utilisateur aurait une propriété de type EmailAddress et le constructeur EmailAddress accepte une chaîne et lève une exception si la chaîne ne correspond pas au format requis. Vous pouvez ensuite réutiliser EmailAddress ValueObject sur d'autres entités qui doivent stocker une adresse e-mail.
Chris Simon
D'accord, je vois pourquoi utiliser Value Object maintenant. Mais cela signifie que l'objet de valeur doit une propriété qui est la règle métier gérant le format?
mfrachet
1
Les objets de valeur doivent être immuables. Généralement, cela signifie que vous initialisez et validez dans le constructeur, et pour toutes les propriétés, utilisez le modèle public get / private set. Mais vous pouvez utiliser des constructions de langage pour définir l'égalité, le processus ToString, etc. par exemple kacper.gunia.me/ddd-building-blocks-in-php-value-object ou github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon
Merci @ChrisSimon, enfin et répondez à une situation DDD réelle qui implique du code et pas seulement de la théorie. J'ai passé 5 jours à parcourir SO et le Web pour un exemple fonctionnel de création et d'enregistrement d'un agrégat, et c'est l'explication la plus claire que j'ai trouvée.
e_i_pi
2

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 eux-mêmes aucune 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 des valeurs d'identité et des 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.

SMS
la source
1

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 des 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? Faites-en ensuite partie intégranteExamQuestion.Answer(string answer)

Cela correspond-il à une autre partie de votre domaine? met ça iciBasket.Purchase(Order order)

Préférez-vous faire des services ADM REST? Alors ok.

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