Lorsque vous suivez SRP, comment dois-je gérer la validation et la sauvegarde des entités?

10

J'ai récemment lu Clean Code et divers articles en ligne sur SOLID, et plus j'en lis, plus j'ai l'impression de ne rien savoir.

Supposons que je construis une application Web à l'aide d'ASP.NET MVC 3. Disons que j'ai un UsersControlleravec une Createaction comme celle-ci:

public class UsersController : Controller
{
    public ActionResult Create(CreateUserViewModel viewModel)
    {

    }
}

Dans cette méthode d'action, je veux enregistrer un utilisateur dans la base de données si les données entrées sont valides.

Maintenant, selon le principe de responsabilité unique, un objet devrait avoir une responsabilité unique, et cette responsabilité devrait être entièrement encapsulée par la classe. Tous ses services devraient être étroitement alignés sur cette responsabilité. Étant donné que la validation et l'enregistrement dans la base de données sont deux responsabilités distinctes, je suppose que je devrais créer une classe distincte pour les gérer comme ceci:

public class UsersController : Controller
{
    private ICreateUserValidator validator;
    private IUserService service;

    public UsersController(ICreateUserValidator validator, IUserService service)
    {
        this.validator = validator;
        this.service= service;
    }

    public ActionResult Create(CreateUserViewModel viewModel)
    {
        ValidationResult result = validator.IsValid(viewModel);

        if (result.IsValid)
        {
            service.CreateUser(viewModel);
            return RedirectToAction("Index");
        }
        else
        {
            foreach (var errorMessage in result.ErrorMessages)
            {
                ModelState.AddModelError(String.Empty, errorMessage);
            }
            return View(viewModel);
        }
    }
}

Cela a un certain sens pour moi, mais je ne suis pas du tout sûr que ce soit la bonne façon de gérer des choses comme ça. Il est par exemple tout à fait possible de passer une instance invalide de CreateUserViewModelà la IUserServiceclasse. Je sais que je pourrais utiliser les DataAnnotations intégrées, mais que faire quand elles ne suffisent pas? Image que mon ICreateUserValidatorvérifie la base de données pour voir s'il existe déjà un autre utilisateur du même nom ...

Une autre option est de laisser le IUserServicesoin de la validation comme ceci:

public class UserService : IUserService
{
    private ICreateUserValidator validator;

    public UserService(ICreateUserValidator validator)
    {
        this.validator = validator;
    }

    public ValidationResult CreateUser(CreateUserViewModel viewModel)
    {
        var result = validator.IsValid(viewModel);

        if (result.IsValid)
        {
            // Save the user
        }

        return result;
    }
}

Mais je sens que je viole le principe de responsabilité unique ici.

Comment dois-je gérer quelque chose comme ça?

Kristof Claes
la source
La userclasse ne devrait-elle pas gérer la validation? SRP ou non, je ne vois pas pourquoi l' userinstance ne devrait pas savoir quand elle est valide ou non et s'appuyer sur autre chose pour le déterminer. Quelles autres responsabilités la classe a-t-elle? De plus, lorsque les usermodifications changent, la validation changera probablement, donc l'externalisation vers une autre classe ne créera qu'une classe étroitement couplée.
sebastiangeiger

Réponses:

4

Je ne pense vraiment pas que vous violiez le principe de responsabilité unique dans votre deuxième exemple.

  • La UserServiceclasse n'a qu'une seule raison de changer: s'il est nécessaire de changer la façon dont vous enregistrez un utilisateur.

  • La ICreateUserValidatorclasse n'a qu'une seule raison de changer: s'il est nécessaire de changer la façon dont vous validez un utilisateur.

Je dois admettre que votre première implémentation est plus intuitive. Cependant, la validation doit être effectuée par l'entité qui crée l'utilisateur. Le créateur lui-même ne devrait pas être responsable de la validation; il devrait plutôt déléguer la responsabilité à une classe de validateur (comme dans votre deuxième implémentation). Donc, je ne pense pas que le deuxième design manque de SRP.

Guven
la source
1
Modèle à responsabilité unique? Principe, peut-être?
yannis
Oui, bien sûr :) Corrigé!
Guven
0

Il me semble que le premier exemple est "plus proche" du vrai SRP; il est de la responsabilité du contrôleur dans votre cas de savoir comment câbler les choses et comment passer le ViewModel.

Ne serait-il pas plus logique que tous les IsValid / ValidationMessages fassent partie du ViewModel lui-même? Je n'ai pas essayé MVVM, mais cela ressemble à l'ancien modèle Model-View-Presenter, où le présentateur pouvait gérer des choses comme ça parce qu'il était directement lié à la présentation. Si votre ViewModel peut vérifier lui-même la validité, il n'y a aucune chance de passer un non valide à la méthode Create du UserService.

Wayne Molina
la source
J'ai toujours pensé que les ViewModels devraient être de simples DTO sans trop de logique. Je ne sais pas si je devrais mettre quelque chose comme vérifier la base de données dans un ViewModel ...
Kristof Claes
Je suppose que cela dépendrait de ce que votre validation implique; si le ViewModel expose simplement le IsValidbooléen et le ValidationMessagestableau, il pourrait toujours appeler dans une classe Validator et ne pas avoir à se soucier de la façon dont la validation est implémentée. Le contrôleur pourrait d'abord vérifier que, par exemple, if (userViewModel.IsValid) { userService.Create(userViewModel); }le ViewModel devrait savoir s'il est valide, mais pas comment il le sait.
Wayne Molina