Je cherche toujours les meilleures pratiques pour la validation de modèle de domaine. Est-ce bien de mettre la validation en constructeur de modèle de domaine? mon exemple de validation de modèle de domaine comme suit:
public class Order
{
private readonly List<OrderLine> _lineItems;
public virtual Customer Customer { get; private set; }
public virtual DateTime OrderDate { get; private set; }
public virtual decimal OrderTotal { get; private set; }
public Order (Customer customer)
{
if (customer == null)
throw new ArgumentException("Customer name must be defined");
Customer = customer;
OrderDate = DateTime.Now;
_lineItems = new List<LineItem>();
}
public void AddOderLine //....
public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}
public class OrderLine
{
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public OrderLine(Order order, int quantity, Product product)
{
if (order == null)
throw new ArgumentException("Order name must be defined");
if (quantity <= 0)
throw new ArgumentException("Quantity must be greater than zero");
if (product == null)
throw new ArgumentException("Product name must be defined");
Order = order;
Quantity = quantity;
Product = product;
}
}
Merci pour toutes vos suggestions.
la source
Malgré le fait que cette question est un peu fade, j'aimerais ajouter quelque chose de valable:
Je voudrais être d'accord avec @MichaelBorgwardt et étendre en évoquant la testabilité. Dans "Travailler efficacement avec le code hérité", Michael Feathers parle beaucoup d'obstacles aux tests et l'un de ces obstacles est "difficile à construire". Construire un objet invalide devrait être possible, et comme le suggère Fowler, les contrôles de validité dépendants du contexte devraient pouvoir identifier ces conditions. Si vous ne savez pas comment construire un objet dans un faisceau de test, vous aurez du mal à tester votre classe.
En ce qui concerne la validité, j'aime penser aux systèmes de contrôle. Les systèmes de contrôle fonctionnent en analysant en permanence l'état d'une sortie et en appliquant une action corrective lorsque la sortie s'écarte du point de consigne, on parle de contrôle en boucle fermée. Le contrôle en boucle fermée attend intrinsèquement des déviations et agit pour les corriger. C'est ainsi que fonctionne le monde réel. C'est pourquoi tous les systèmes de contrôle réels utilisent généralement des contrôleurs en boucle fermée.
Je pense que l'utilisation d'une validation dépendant du contexte et d'une construction facile des objets rendra votre système plus facile à utiliser.
la source
Comme je suis sûr que tu le sais déjà ...
Vérifier la validité des données transmises en tant que paramètres c'tor est définitivement valide dans le constructeur - sinon, vous autorisez éventuellement la construction d'un objet non valide.
Cependant (et c'est juste mon opinion, je ne trouve aucune bonne documentation à ce stade) - si la validation des données nécessite des opérations complexes (telles que des opérations asynchrones - peut-être une validation sur serveur si vous développez une application de bureau), il est préférable de mettre dans une fonction d'initialisation ou de validation explicite d'une certaine sorte et les membres mis à des valeurs par défaut (telles que
null
) dans le c'tor.En outre, juste comme une note latérale que vous avez inclus dans votre exemple de code ...
À moins que vous ne procédiez à une validation supplémentaire (ou à une autre fonctionnalité)
AddOrderLine
, j'exposerais probablement laList<LineItem>
propriété comme une propriété plutôt que d'Order
agir comme une façade .la source
AddLineItem
méthode. En fait, pour DDD, c'est préférable. SiList<LineItem>
est remplacé par un objet de collection personnalisé, la propriété exposée et tout ce qui en dépendList<LineItem>
sont sujets à modification, erreur et exception.La validation doit être effectuée dès que possible.
La validation dans n’importe quel contexte, que ce soit un modèle de domaine ou tout autre moyen d’écrire un logiciel, doit servir à CE QUE vous voulez valider et à quel niveau vous vous trouvez actuellement.
Sur la base de votre question, je suppose que la réponse serait de scinder la validation.
La validation de propriété vérifie si la valeur de cette propriété est correcte, par exemple lorsqu'une plage comprise entre 1 et 10 est spécifiée.
La validation d'objet garantit que toutes les propriétés de l'objet sont valides conjointement. Par exemple, BeginDate est avant EndDate. Supposons que vous lisiez une valeur du magasin de données et que BeginDate et EndDate soient initialisés à DateTime.Min par défaut. Lors de la définition de BeginDate, il n'y a aucune raison d'appliquer la règle "doit être avant EndDate", car cela ne s'applique pas ENCORE. Cette règle doit être vérifiée APRÈS que toutes les propriétés aient été définies. Ceci peut être appelé au niveau racine agrégé
La validation doit également être effectuée sur l'entité agrégée (ou racine agrégée). Un objet Order peut contenir des données valides, tout comme ses OrderLines. Mais une règle de gestion stipule qu’aucun ordre ne peut dépasser 1 000 dollars. Comment appliqueriez-vous cette règle dans certains cas, cela EST autorisé. vous ne pouvez pas simplement ajouter une propriété "ne pas valider le montant" car cela entraînerait des abus (tôt ou tard, peut-être même vous, juste pour obtenir cette "vilaine demande" de la route).
Ensuite, il y a validation au niveau de la couche de présentation. Allez-vous vraiment envoyer l'objet sur le réseau, sachant qu'il échouera? Ou allez-vous épargner ce burdon à l'utilisateur et l'informer dès qu'il entrera une valeur invalide. Par exemple, la plupart du temps, votre environnement DEV sera plus lent que la production. Souhaitez-vous attendre 30 secondes avant d’être informé de "vous avez de nouveau oublié ce champ pendant un autre test", en particulier lorsqu’un bug de production doit être corrigé alors que votre patron vous respire?
La validation au niveau de la persistance est supposée être aussi proche que possible de la validation de la valeur de la propriété. Cela aidera à éviter les exceptions avec la lecture des erreurs "NULL" ou "Valeur invalide" lors de l'utilisation de mappeurs de tout type ou de lecteurs de données anciens L'utilisation de procédures stockées résout ce problème, mais nécessite d'écrire AGAIN et de l'exécuter AGAIN. Et les procédures stockées sont le domaine d'administration de la base de données, alors n'essayez pas de faire son travail aussi bien (ou pire, dérangez-le avec ce «médiocre choix pour lequel il n'est pas payé».
pour le dire avec quelques mots célèbres "ça dépend", mais au moins vous savez maintenant POURQUOI ça dépend.
J'aimerais pouvoir regrouper tout cela dans un seul endroit, mais malheureusement, cela ne peut être fait. Cela créerait une dépendance sur un "objet Dieu" contenant TOUTES les validations pour TOUTES les couches. Vous ne voulez pas aller dans cette voie sombre.
Pour cette raison, je ne lance que des exceptions de validation au niveau de la propriété. Tous les autres niveaux, j'utilise ValidationResult avec une méthode IsValid pour rassembler toutes les "règles brisées" et les transmettre à l'utilisateur dans une seule exception AggregateException.
Lors de la propagation de la pile d'appels, je les rassemble à nouveau dans AggregateExceptions jusqu'à atteindre la couche de présentation. La couche service peut envoyer cette exception directement au client dans le cas de WCF en tant qu'exception FaultException.
Cela me permet de prendre l'exception et de la scinder pour afficher les erreurs individuelles à chaque contrôle d'entrée ou de l'aplatir et de l'afficher dans une seule liste. Le choix t'appartient.
c'est pourquoi j'ai également évoqué la validation de la présentation, afin de les court-circuiter autant que possible.
Dans le cas où vous vous demandez pourquoi je dispose également de la validation au niveau de l'agrégation (ou du niveau de service si vous le souhaitez), c'est parce que je n'ai pas de boule de cristal me disant qui utilisera mes services dans le futur. Vous aurez suffisamment de difficulté à trouver vos propres erreurs pour empêcher les autres de commettre les mêmes erreurs que vous :) en saisissant des données non valides.Vous administrez l'application A, mais l'application B alimente certaines données à l'aide de votre service. Devinez qui ils demandent en premier quand il y a un bug? L’administrateur de l’application B informera volontiers l’utilisateur "il n’ya pas d’erreur de ma part, j’ai simplement introduit les données".
la source