Ao seguir o SRP, como devo lidar com validar e salvar entidades?

10

Ultimamente, tenho lido o Clean Code e vários artigos on-line sobre o SOLID. Quanto mais leio sobre isso, mais sinto que não sei de nada.

Digamos que estou criando um aplicativo da Web usando o ASP.NET MVC 3. Digamos que eu tenha um UsersControllercom uma Createação como esta:

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

    }
}

Nesse método de ação, quero salvar um usuário no banco de dados se os dados inseridos forem válidos.

Agora, de acordo com o Princípio da Responsabilidade Única, um objeto deve ter uma única responsabilidade e essa responsabilidade deve ser totalmente encapsulada pela classe. Todos os seus serviços devem estar estreitamente alinhados com essa responsabilidade. Como validação e salvamento no banco de dados são duas responsabilidades separadas, acho que devo criar uma classe separada para lidar com elas dessa maneira:

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);
        }
    }
}

Isso faz algum sentido para mim, mas não tenho certeza de que esse seja o caminho certo para lidar com coisas assim. Por exemplo, é perfeitamente possível passar uma instância inválida CreateUserViewModelpara a IUserServiceclasse. Eu sei que poderia usar o DataAnnotations incorporado, mas e quando eles não forem suficientes? Imagem que meu ICreateUserValidatorverifica o banco de dados para ver se já existe outro usuário com o mesmo nome ...

Outra opção é deixar que IUserServicea validação tome conta da seguinte maneira:

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;
    }
}

Mas sinto que estou violando o Princípio da Responsabilidade Única aqui.

Como devo lidar com algo assim?

Kristof Claes
fonte
A userclasse não deve lidar com a validação? SRP ou não, não vejo por que a userinstância não deve saber quando é válida ou não e confiar em algo mais para determinar isso. Que outras responsabilidades a classe tem? Além disso, quando as useralterações a validação provavelmente mudar, a terceirização que para uma classe diferente criará apenas uma classe fortemente acoplada.
sebastiangeiger

Respostas:

4

Realmente não acho que você esteja violando o princípio de responsabilidade única no seu segundo exemplo.

  • A UserServiceclasse tem apenas um motivo para mudar: se houver necessidade de alterar a maneira como você salva um usuário.

  • A ICreateUserValidatorclasse tem apenas um motivo para alterar: se houver necessidade de alterar a maneira como você valida um usuário.

Devo admitir que sua primeira implementação é mais intuitiva. No entanto, a validação deve ser feita pela entidade que cria o usuário. O próprio criador não deve ser responsável pela validação; em vez disso, deve delegar a responsabilidade para uma classe de validador (como em sua segunda implementação). Portanto, não acho que o segundo design não tenha SRP.

Guven
fonte
11
Padrão de responsabilidade única? Princípio, talvez?
yannis
Sim, claro :) Corrigido!
Guven
0

Parece-me que o primeiro exemplo é "mais próximo" do SRP verdadeiro; é responsabilidade do controlador no seu caso saber como conectar as coisas e como transmitir o ViewModel.

Não faria mais sentido que IsValid / ValidationMessages inteiro fizesse parte do próprio ViewModel? Eu não brinquei com o MVVM, mas parece que o antigo padrão Model-View-Presenter, onde era aceitável o Presenter lidar com coisas assim porque estava diretamente relacionado à apresentação. Se o seu ViewModel puder verificar a validade, não há chance de passar um inválido para o método Create do UserService.

Wayne Molina
fonte
Eu sempre pensei que os ViewModels deveriam ser simples DTOs sem muita lógica neles. Eu não tenho certeza se eu deveria colocar algo como verificar o banco de dados em um ViewModel ...
Kristof Claes
Eu acho que isso dependeria do que sua validação implica; se o ViewModel apenas expuser o IsValidbooleano e o ValidationMessagesarray, ele ainda poderá chamar uma classe Validator e não precisar se preocupar com o modo como a validação está sendo implementada. O controlador pode verificar se primeiro, por exemplo, if (userViewModel.IsValid) { userService.Create(userViewModel); }basicamente o ViewModel deve saber se é válido, mas não como ele é válido.
Wayne Molina