69 votes

Où mettre la validation des règles globales dans DDD

Je suis nouveau sur DDD, et j'essaie de l'appliquer dans la vie réelle. Il n'y a pas de questions à propos de telle logique de validation, null vérifier, les cordes à vide, etc - qui va directement à l'entité constructeur/propriété. Mais où mettre la validation de certaines règles comme "Unique" nom d'utilisateur?

Donc, nous avons entité Utilisateur

public class User : IAggregateRoot
{
   private string _name;

   public string Name
   {
      get { return _name; }
      set { _name = value; }
   }

   // other data and behavior
}

Et de référentiel pour les utilisateurs

public interface IUserRepository : IRepository<User>
{
   User FindByName(string name);
}

Les Options sont les suivantes:

  1. Injecter référentiel de l'entité
  2. Injecter référentiel à l'usine
  3. L'opération de création sur le domaine de service
  4. ???

Et chaque option est la plus détaillée:

1 .Injecter référentiel de l'entité

Je peux interroger le référentiel dans des entités constructeur/propriété. Mais je pense que garder de référence du référentiel au sein de l'entité est une mauvaise odeur.

public User(IUserRepository repository)
{
    _repository = repository;
}

public string Name
{
    get { return _name; }
    set 
    {
       if (_repository.FindByName(value) != null)
          throw new UserAlreadyExistsException();

       _name = value; 
    }
}

Mise à jour: On peut l'utiliser DI pour masquer la dépendance entre l'Utilisateur et IUserRepository via la Spécification de l'objet.

2. Injecter référentiel à l'usine

Je peux mettre cette vérification logique dans UserFactory. Mais que faire si nous voulons changer le nom de l'utilisateur?

3. L'opération de création sur le domaine de service

Je peux créer le domaine du service pour la création et la modification des utilisateurs. Mais quelqu'un peut directement modifier le nom de l'utilisateur sans appel à ce service...

public class AdministrationService
{
    private IUserRepository _userRepository;

    public AdministrationService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void RenameUser(string oldName, string newName)
    {
        if (_userRepository.FindByName(newName) != null)
            throw new UserAlreadyExistException();

        User user = _userRepository.FindByName(oldName);
        user.Name = newName;
        _userRepository.Save(user);
    }
}

4. ???

Où mettez-vous mondial de la logique de validation pour les entités?

Merci!

60voto

Marijn Points 6311

La plupart du temps, il est préférable de placer ces types de règles en Specificationobjets. Vous pouvez placer ces Specifications dans votre domaine de paquets, ainsi si quelqu'un utilise votre domaine paquet a accès à eux. À l'aide d'une spécification, vous pouvez regrouper vos règles d'entreprise avec vos entités, sans créer difficiles à lire les entités indésirables avec dépendances sur les services et les référentiels. Si nécessaire, vous pouvez injecter des dépendances sur des services ou des référentiels dans un cahier des charges.

Selon le contexte, vous pouvez construire différents validateurs l'aide de la spécification des objets.

Principale préoccupation des entités doivent garder la trace des affaires de l'état - c'est assez d'une responsabilité et qu'ils ne devraient pas être concernés par la validation.

Exemple

public class User
{
    public string Id { get; set; }
    public string Name { get; set; }
}

Deux caractéristiques:

public class IdNotEmptySpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User subject)
    {
        return !string.IsNullOrEmpty(subject.Id);
    }
}


public class NameNotTakenSpecification : ISpecification<User>
{
    // omitted code to set service; better use DI
    private Service.IUserNameService UserNameService { get; set; } 

    public bool IsSatisfiedBy(User subject)
    {
        return UserNameService.NameIsAvailable(subject.Name);
    }
}

Et un validateur:

public class UserPersistenceValidator : IValidator<User>
{
    private readonly IList<ISpecification<User>> Rules =
        new List<ISpecification<User>>
            {
                new IdNotEmptySpecification(),
                new NameNotEmptySpecification(),
                new NameNotTakenSpecification()
                // and more ... better use DI to fill this list
            };

    public bool IsValid(User entity)
    {
        return BrokenRules(entity).Count() > 0;
    }

    public IEnumerable<string> BrokenRules(User entity)
    {
        return Rules.Where(rule => !rule.IsSatisfiedBy(entity))
                    .Select(rule => GetMessageForBrokenRule(rule));
    }

    // ...
}

Par souci d'exhaustivité, les interfaces:

public interface IValidator<T>
{
    bool IsValid(T entity);
    IEnumerable<string> BrokenRules(T entity);
}

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T subject);
}

Notes

Je pense que Vijay Patel antérieure de la réponse est dans la bonne direction, mais je pense que c'est un peu hors. Il suggère que l'entité utilisateur dépend de la spécification, où j'ai la conviction que ce devrait être l'inverse. De cette façon, vous pouvez laisser la spécification de dépendre des services, des dépôts et le contexte en général, sans faire de votre entité dépendent d'eux à travers un cahier des charges de la dépendance.

Références

Une question connexe, avec une bonne réponse avec un exemple: la Validation dans un Domain Driven Design.

Eric Evans décrit l'utilisation de la spécification du modèle de validation, de sélection et de construction de l'objet dans le chapitre 9, pp 145.

Cet article sur la spécification du modèle avec une application de dans .Net pourrait être d'intérêt pour vous.

12voto

George Polevoy Points 2751

Je ne recommanderais pas refuse de modifier les propriétés de l'entité, si c'est une entrée de l'utilisateur. Par exemple, si la validation ne passe pas, vous pouvez toujours utiliser l'instance pour l'afficher dans l'interface utilisateur avec les résultats de la validation, permettant à l'utilisateur de corriger l'erreur.

Jimmy Nilsson dans son "Application Domain-Driven Design et les Modèles" recommande de valider pour une opération particulière, non seulement pour la persistance. Alors qu'une entité pourrait être persisté, la véritable validation se produit lorsqu'une entité est sur le point de changer, c'est l'état, par exemple, de "Ordonnée" les modifications de l'état de "Acheté".

Lors de la création, l'instance doit être valide pour l'économie, ce qui implique la vérification de l'unicité. C'est différent de valable pour la commande, où non seulement l'unicité doit être vérifié, mais aussi, par exemple, la crédibilité d'un client, et de la disponibilité en magasin.

Ainsi, la logique de validation ne doit pas être appelée sur un affectations de propriété, il doit être appelée sur la somme totale des opérations, si elles sont persistantes ou pas.

7voto

Niels van der Rest Points 11802

Edit: à en Juger par les autres réponses, le nom correct pour un tel "service de domaine" est la spécification. J'ai mis à jour ma réponse à refléter ce, y compris un examen plus détaillé de code de l'échantillon.

J'irais avec l'option 3, la création d'un service de domaine de spécification qui encapsule la logique réelle qui effectue la validation. Par exemple, le cahier des charges initialement appelle un référentiel, mais vous pouvez le remplacer avec un appel de service web à un stade ultérieur. Avoir tout ce que la logique derrière un résumé spécification de garder l'ensemble de la conception plus souple.

Pour empêcher quelqu'un d'en éditer le nom, sans le valider, faire de la spécification d'un aspect nécessaire de modifier le nom. Vous pouvez réaliser cette opération en changeant l'API de votre entité à quelque chose comme ceci:

public class User
{
    public string Name { get; private set; }

    public void SetName(string name, ISpecification<User, string> specification)
    {
        // Insert basic null validation here.

        if (!specification.IsSatisfiedBy(this, name))
        {
            // Throw some validation exception.
        }

        this.Name = name;
    }
}

public interface ISpecification<TType, TValue>
{
    bool IsSatisfiedBy(TType obj, TValue value);
}

public class UniqueUserNameSpecification : ISpecification<User, string>
{
    private IUserRepository repository;

    public UniqueUserNameSpecification(IUserRepository repository)
    {
        this.repository = repository;
    }

    public bool IsSatisfiedBy(User obj, string value)
    {
        if (value == obj.Name)
        {
            return true;
        }

        // Use this.repository for further validation of the name.
    }
}

Votre code d'appel devrait ressembler à quelque chose comme ceci:

var userRepository = IoC.Resolve<IUserRepository>();
var specification = new UniqueUserNameSpecification(userRepository);

user.SetName("John", specification);

Et bien sûr, vous pouvez vous moquer ISpecification dans vos tests unitaires pour tester plus facilement.

3voto

Vijay Patel Points 5696

Je voudrais utiliser une spécification pour encapsuler la règle. Vous pouvez ensuite appeler lorsque la propriété UserName est mise à jour (ou à partir de tout autre endroit qui pourrait en avoir besoin):

 public class UniqueUserNameSpecification : ISpecification
{
  public bool IsSatisifiedBy(User user)
  {
     // Check if the username is unique here
  }
}

public class User
{
   string _Name;
   UniqueUserNameSpecification _UniqueUserNameSpecification;  // You decide how this is injected 

   public string Name
   {
      get { return _Name; }
      set
      {
        if (_UniqueUserNameSpecification.IsSatisifiedBy(this))
        {
           _Name = value;
        }
        else
        {
           // Execute your custom warning here
        }
      }
   }
}
 

Peu importe si un autre développeur tente de modifier User.Name directement, car la règle sera toujours exécutée.

En savoir plus ici

2voto

Kdeveloper Points 7034

Je ne suis pas un expert sur DDD mais je me suis posé les mêmes questions et c'est ce que je suis venu avec: La Validation de la logique devrait normalement aller dans le constructeur/l'usine et les setters. De cette manière, vous garantissez que vous avez toujours valide objets du domaine. Mais si la validation implique des requêtes de base de données ayant un impact sur vos performances, une mise en œuvre efficace nécessite une conception différente.

(1) l'Injection Entités: l'Injection entités peuvent être d'ordre technique difficile et fait également de la gestion de la performance de l'application très difficile en raison de la fragmentation de vous logique de base de données. Apparemment opérations simples peuvent maintenant avoir beaucoup d'impact sur les performances. Il rend également impossible pour optimiser votre domaine d'objet pour les opérations sur les groupes du même genre d'entités, vous ne pouvez plus écrire une seule requête du groupe, et au lieu de cela, vous avez toujours les requêtes individuelles pour chaque entité.

(2) l'Injection de référentiel: il ne faut pas mettre toute la logique métier dans des référentiels. Garder les référentiels simple et ciblée. Ils agissent comme s'ils étaient des collections et contiennent seulement de la logique pour l'ajout, la suppression et la recherche d'objets (certains même de la scission, les méthodes de recherche à d'autres objets).

(3) service de Domaine de Ce qui me semble le plus logique pour gérer la validation qui nécessite l'interrogation de bases de données. Une bonne mise en œuvre ferait le constructeur/l'usine et les poseurs concernés colis privé, de sorte que les entités ne peuvent être créés / modifiés avec le service de domaine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X