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 UsersController
com uma Create
açã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 CreateUserViewModel
para a IUserService
classe. Eu sei que poderia usar o DataAnnotations incorporado, mas e quando eles não forem suficientes? Imagem que meu ICreateUserValidator
verifica o banco de dados para ver se já existe outro usuário com o mesmo nome ...
Outra opção é deixar que IUserService
a 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?
fonte
user
classe não deve lidar com a validação? SRP ou não, não vejo por que auser
instâ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 asuser
alterações a validação provavelmente mudar, a terceirização que para uma classe diferente criará apenas uma classe fortemente acoplada.Respostas:
Realmente não acho que você esteja violando o princípio de responsabilidade única no seu segundo exemplo.
A
UserService
classe tem apenas um motivo para mudar: se houver necessidade de alterar a maneira como você salva um usuário.A
ICreateUserValidator
classe 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.
fonte
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.
fonte
IsValid
booleano e oValidationMessages
array, 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.