Question

Cette question est agnostique en langage mais je suis un gars de C #, j'utilise le terme POCO pour désigner un objet qui ne préforme que le stockage de données, généralement à l'aide de champs de lecture et de définition.

Je viens de retravailler mon modèle de domaine pour qu'il devienne un POCO super duper et il reste quelques soucis quant à la manière de s'assurer que les valeurs de propriété ont un sens dans le domaine.

Par exemple, la date de fin d'un service ne doit pas dépasser la date de fin du contrat du service. Toutefois, le fait de placer le chèque dans le paramètre Service.EndDate est une violation de SOLID, sans compter que plus le nombre de validations à effectuer augmente, plus le nombre de validations de classes POCO deviendrait important.

J'ai quelques solutions (je posterai des réponses), mais elles ont leurs inconvénients et je me demande quelles sont les approches préférées pour résoudre ce dilemme?

Était-ce utile?

La solution

Je pense que vous commencez avec une mauvaise hypothèse, c’est-à-dire que vous devriez avoir des objets qui ne font que stocker des données et ne pas avoir de méthodes mais des accesseurs. Le but des objets est d'encapsuler les données et les comportements . Si vous avez une chose qui est juste, fondamentalement, une structure, quels comportements encapsulez-vous?

Autres conseils

J'entends toujours les gens argumenter en faveur d'un "Valider". ou " IsValid " méthode.

Personnellement, je pense que cela peut fonctionner, mais avec la plupart des projets DDD, vous vous retrouvez généralement avec plusieurs validations autorisées en fonction de l'état spécifique de l'objet.

Je préfère donc "IsValidForNewContract", "IsValidForTermination". ou similaire, parce que je pense que la plupart des projets aboutissent à plusieurs validateurs / états de ce type par classe. Cela signifie également que je n'ai pas d'interface, mais que je peux écrire des validateurs agrégés qui read reflètent très bien les conditions commerciales que j'affirme.

Je crois vraiment que les solutions génériques dans ce cas détournent très souvent l'attention de ce qui est important - ce que fait le code - pour un très léger gain en élégance technique (l'interface, le délégué, etc.). ). Votez-moi pour cela;)

Un de mes collègues a eu une idée qui a plutôt bien fonctionné. Nous n’avons jamais eu un très bon nom, mais nous l’avons appelé inspecteur / juge.

L’Inspecteur examinerait un objet et vous dirait toutes les règles qu’il violait. Le juge déciderait quoi faire à ce sujet. Cette séparation nous a permis de faire quelques choses. Cela nous a permis de mettre toutes les règles au même endroit (inspecteur), mais nous pourrions avoir plusieurs juges et choisir le juge en fonction du contexte.

Un exemple d'utilisation de plusieurs juges concerne la règle selon laquelle un client doit avoir une adresse. C'était une application standard à trois niveaux. Au niveau de l'interface utilisateur, le juge génère quelque chose que l'interface utilisateur peut utiliser pour indiquer les champs à renseigner. Le juge de l'interface utilisateur n'a pas déclenché d'exception. Dans la couche service, il y avait un autre juge. S'il trouve un client sans adresse pendant l'enregistrement, une exception est levée. À ce stade, vous devez vraiment empêcher les choses de progresser.

Nous avons également eu des juges plus stricts à mesure que l'état des objets changeait. Il s’agissait d’une demande d’assurance et au cours du processus de cotation, une police pouvait être sauvegardée dans un état incomplet. Mais une fois que cette politique était prête à être rendue active, il fallait définir beaucoup de choses. Ainsi, le juge citant côté service n'était pas aussi strict que le juge d'activation. Cependant, les règles utilisées dans l'inspecteur étaient toujours les mêmes et vous pouviez toujours savoir ce qui n'était pas complet, même si vous décidiez de ne rien faire.

Une solution consiste à ce que DataAccessObject de chaque objet prenne une liste de validateurs. Lorsque Save est appelé, il vérifie chaque validateur:

public class ServiceEndDateValidator : IValidator<Service> {
  public void Check(Service s) {
    if(s.EndDate > s.Contract.EndDate)
      throw new InvalidOperationException();
  }
}

public class ServiceDao : IDao<Service> {
  IValidator<Service> _validators;
  public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;}
  public void Save(Service s) {
    foreach(var v in _validators)
      v.Check(service);
    // Go on to save
  }
}

L’avantage, c’est très clair sur SoC, l’inconvénient est que nous n’obtenons pas le chèque avant l’appel de Save ().

Dans le passé, je déléguais généralement la validation à un service tel que ValidationService. C’est en principe toujours conforme à la philosophie de DDD.

En interne, cela contiendrait une collection de Validators et un ensemble très simple de méthodes publiques telles que Validate (), qui pourraient renvoyer une collection d'objets d'erreur.

Très simplement, quelque chose comme ça en C #

public class ValidationService<T>
{
  private IList<IValidator> _validators;

  public IList<Error> Validate(T objectToValidate)
  {
    foreach(IValidator validator in _validators)
    {
      yield return validator.Validate(objectToValidate);
    }
  }
}

Les validateurs peuvent être ajoutés à un constructeur par défaut ou injectés via une autre classe telle que ValidationServiceFactory.

Je pense que ce serait probablement le meilleur endroit pour la logique, en fait, mais ce n'est que moi. Vous pourriez avoir une sorte de méthode IsValid qui vérifie aussi toutes les conditions et renvoie vrai / faux, peut-être une sorte de collection ErrorMessages mais c'est un sujet douteux puisque les messages d'erreur ne font pas vraiment partie du modèle de domaine. Je suis un peu partial, j'ai déjà travaillé avec RoR et c'est essentiellement ce que font ses modèles.

Une autre possibilité consiste à implémenter chacune de mes classes

public interface Validatable<T> {
  public event Action<T> RequiresValidation;
}

Et demandez à chaque passeur de chaque classe de déclencher l'événement avant de le paramétrer (je pourrais peut-être y parvenir via des attributs).

L’avantage est la vérification de validation en temps réel. Mais le code est plus compliqué et on ne sait pas qui devrait faire l’attachement.

Voici une autre possibilité. La validation est effectuée via un proxy ou un décorateur sur l'objet Domaine:

public class ServiceValidationProxy : Service {
  public override DateTime EndDate {
    get {return EndDate;}
    set {
      if(value > Contract.EndDate)
        throw new InvalidOperationexception();
      base.EndDate = value;
    }
  }
}

Avantage: validation instantanée. Peut facilement être configuré via un IoC.

Inconvénient: si un proxy, les propriétés validées doivent être virtuelles, s'il est décorateur, tous les modèles de domaine doivent être basés sur une interface. Les classes de validation finiront par être un peu lourdes - les mandataires doivent hériter de la classe et les décorateurs doivent implémenter toutes les méthodes. Nommer et organiser peut être déroutant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top