Partager via


Validations de conception dans la couche de modèle de domaine

Conseil / Astuce

Ce contenu est un extrait du livre .NET Microservices Architecture for Containerized .NET Applications, disponible sur .NET Docs ou en tant que PDF téléchargeable gratuitement pour une lecture hors ligne.

Miniature de la couverture du livre électronique sur l'architecture de microservices .NET pour les applications .NET conteneurisées.

Dans DDD, les règles de validation peuvent être considérées comme des invariants. La principale responsabilité d’un agrégat est d’appliquer des invariants lors des modifications d’état pour toutes les entités de cet agrégat.

Les entités de domaine doivent toujours être des entités valides. Il existe un certain nombre d’invariants pour un objet qui doit toujours être vrai. Par exemple, un objet d’élément de commande doit toujours avoir une quantité qui doit être un entier positif, ainsi qu’un nom d’article et un prix. Par conséquent, l’application des invariants est la responsabilité des entités de domaine (en particulier de la racine d’agrégation) et un objet d’entité ne doit pas être en mesure d’exister sans être valide. Les règles invariantes sont simplement exprimées en tant que contrats, et des exceptions ou des notifications sont déclenchées lorsqu’elles sont violées.

Le raisonnement derrière cela est que de nombreux bogues se produisent parce que les objets sont dans un état qu’ils ne devraient jamais avoir été dans.

Nous proposons maintenant de mettre en place un SendUserCreationEmailService qui prend un UserProfile ... comment pouvons-nous justifier que dans ce service le nom n’est pas nul ? Est-ce que nous le vérifions à nouveau ? Ou plus probablement, vous ne prenez pas la peine de vérifier et vous espérez que tout ira pour le mieux; vous comptez sur quelqu’un qui a pris la peine de le valider avant de vous l’envoyer. Bien sûr, en utilisant le TDD, l'un des premiers tests que nous devrions écrire est de vérifier que si nous envoyons un client avec un nom nul, cela doit déclencher une erreur. Mais une fois que nous commençons à écrire ces types de tests encore et encore, nous réalisons ... « Que se passe-t-il si nous n’avons jamais autorisé le nom à devenir null ? nous n’aurions pas tous ces tests !".

Implémenter des validations dans la couche de modèle de domaine

Les validations sont généralement implémentées dans les constructeurs d’entités de domaine ou dans les méthodes qui peuvent mettre à jour l’entité. Il existe plusieurs façons d’implémenter des validations, telles que la vérification des données et la levée d’exceptions en cas d’échec de la validation. Il existe également des modèles plus avancés tels que l’utilisation du modèle de spécification pour les validations, et le modèle de notification pour retourner une collection d’erreurs au lieu de renvoyer une exception pour chaque validation au fur et à mesure qu’il se produit.

Valider les conditions et lever des exceptions

L’exemple de code suivant montre l’approche la plus simple de la validation dans une entité de domaine en soulevant une exception. Dans le tableau références à la fin de cette section, vous pouvez voir des liens vers des implémentations plus avancées en fonction des modèles que nous avons abordés précédemment.

public void SetAddress(Address address)
{
    _shippingAddress = address?? throw new ArgumentNullException(nameof(address));
}

Un meilleur exemple illustre la nécessité de s’assurer que l’état interne n’a pas changé ou que toutes les mutations d’une méthode se sont produites. Par exemple, l’implémentation suivante laisse l’objet dans un état non valide :

public void SetAddress(string line1, string line2,
    string city, string state, int zip)
{
    _shippingAddress.line1 = line1 ?? throw new ...
    _shippingAddress.line2 = line2;
    _shippingAddress.city = city ?? throw new ...
    _shippingAddress.state = (IsValid(state) ? state : throw new …);
}

Si la valeur de l’état n’est pas valide, la première ligne d’adresse et la ville ont déjà été modifiées. Cela peut rendre l’adresse non valide.

Une approche similaire peut être utilisée dans le constructeur de l’entité, ce qui déclenche une exception pour vous assurer que l’entité est valide une fois qu’elle est créée.

Utiliser des attributs de validation dans le modèle en fonction des annotations de données

Les annotations de données, telles que les attributs Required ou MaxLength, peuvent être utilisées pour configurer les propriétés de champ de base de données EF Core, comme expliqué en détail dans la section Mappage de table, mais elles ne fonctionnent plus pour la validation d’entité dans EF Core (ni la IValidatableObject.Validate méthode), comme elles l’ont fait depuis EF 4.x dans .NET Framework.

Les annotations de données et l’interface IValidatableObject peuvent toujours être utilisées pour la validation de modèle pendant la liaison de modèle, avant l’appel d’actions du contrôleur comme d’habitude, mais ce modèle est destiné à être un ViewModel ou un DTO et c’est un problème MVC ou API qui n’est pas un problème de modèle de domaine.

Après avoir clairement fait la différence conceptuelle, vous pouvez toujours utiliser des annotations de données et IValidatableObject dans la classe d’entité pour la validation, si vos actions reçoivent un paramètre d’objet de classe d’entité, ce qui n’est pas recommandé. Dans ce cas, la validation se produit lors de la liaison du modèle, juste avant d’appeler l’action, et vous pouvez consulter la propriété ModelState.IsValid du contrôleur pour voir si le résultat est correct. Toutefois, cela se produit dans le contrôleur, pas avant de persister l'objet d'entité dans le DbContext, comme c'était le cas depuis EF 4.x.

Vous pouvez toujours implémenter une validation personnalisée dans la classe d’entité à l’aide d’annotations de données et de la IValidatableObject.Validate méthode, en remplaçant la méthode SaveChanges de DbContext.

Vous pouvez voir un exemple d’implémentation pour valider des IValidatableObject entités dans ce commentaire sur GitHub. Cet exemple ne fait pas de validations basées sur des attributs, mais leur implémentation est facile en utilisant la réflexion dans la même surcharge.

Toutefois, à partir d’un point de vue DDD, le modèle de domaine est mieux conservé avec l’utilisation d’exceptions dans les méthodes de comportement de votre entité, ou en implémentant les modèles de spécification et de notification pour appliquer des règles de validation.

Il peut être judicieux d’utiliser des annotations de données au niveau de la couche application dans les classes ViewModel (au lieu d’entités de domaine) qui acceptent l’entrée, pour permettre la validation du modèle dans la couche d’interface utilisateur. Toutefois, cela ne doit pas être effectué à l’exclusion de la validation dans le modèle de domaine.

Valider des entités en implémentant le modèle de spécification et le modèle de notification

Enfin, une approche plus élaborée de l’implémentation des validations dans le modèle de domaine consiste à implémenter le modèle de spécification conjointement avec le modèle de notification, comme expliqué dans certaines des ressources supplémentaires répertoriées plus loin.

Il convient de noter que vous pouvez également utiliser l’un de ces modèles - par exemple, valider manuellement avec des instructions conditionnelles, mais utiliser le modèle de notification pour empiler et renvoyer une liste d’erreurs de validation.

Utiliser la validation différée dans le domaine

Il existe différentes approches pour traiter les validations différées dans le domaine. Dans son livre Implémentation Domain-Driven Design, Vaughn Vernon traite de ceux-ci dans la section sur la validation.

Validation en deux étapes

Envisagez également la validation en deux étapes. Utilisez la validation au niveau du champ sur vos objets de transfert de données de commande (DTO) et la validation au niveau du domaine à l’intérieur de vos entités. Pour ce faire, retournez un objet de résultat au lieu d’exceptions afin de faciliter la gestion des erreurs de validation.

À l’aide de la validation de champ avec des annotations de données, par exemple, vous ne dupliquez pas la définition de validation. Toutefois, l’exécution peut être côté serveur et côté client dans le cas d’objets DTOs (commandes et ViewModels, par exemple).

Ressources supplémentaires