Cuando sigo SRP, ¿cómo debo tratar con las entidades de validación y guardado?

10

Últimamente he estado leyendo Clean Code y varios artículos en línea sobre SOLID, y cuanto más leo sobre él, más siento que no sé nada.

Digamos que estoy construyendo una aplicación web usando ASP.NET MVC 3. Digamos que tengo UsersControlleruna Createacción como esta:

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

    }
}

En ese método de acción, quiero guardar a un usuario en la base de datos si los datos ingresados ​​son válidos.

Ahora, de acuerdo con el Principio de Responsabilidad Única, un objeto debe tener una responsabilidad única, y esa responsabilidad debe estar completamente encapsulada por la clase. Todos sus servicios deben estar estrechamente alineados con esa responsabilidad. Dado que la validación y el guardado en la base de datos son dos responsabilidades separadas, creo que debería crear una clase separada para manejarlas así:

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

Eso tiene algún sentido para mí, pero no estoy del todo seguro de que esta sea la forma correcta de manejar cosas como esta. Por ejemplo, es completamente posible pasar una instancia no válida CreateUserViewModela la IUserServiceclase. Sé que podría usar las anotaciones de datos integradas, pero ¿qué sucede cuando no son suficientes? Imagen que mi ICreateUserValidatorcomprueba en la base de datos para ver si ya hay otro usuario con el mismo nombre ...

Otra opción es dejar que IUserServicese encargue de la validación de esta manera:

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

Pero siento que estoy violando el Principio de Responsabilidad Única aquí.

¿Cómo debo lidiar con algo como esto?

Kristof Claes
fuente
¿No debería la userclase manejar la validación? SRP o no, no veo por qué la userinstancia no debería saber cuándo es válida o no y confiar en otra cosa para determinar eso. ¿Qué otras responsabilidades tiene la clase? Además, cuando usercambien, la validación probablemente cambiará, por lo que la contratación externa a una clase diferente solo creará una clase estrechamente acoplada.
sebastiangeiger

Respuestas:

4

Realmente no creo que estés violando el Principio de Responsabilidad Individual en tu segundo ejemplo.

  • La UserServiceclase solo tiene una razón para cambiar: si es necesario cambiar la forma en que guarda un usuario.

  • La ICreateUserValidatorclase solo tiene una razón para cambiar: si es necesario cambiar la forma de validar a un usuario.

Debo admitir que su primera implementación es más intuitiva. Sin embargo, la validación debe ser realizada por la entidad que crea el usuario. El creador en sí no debería ser responsable de la validación; más bien debería delegar la responsabilidad a una clase de validador (como en su segunda implementación). Entonces, no creo que el segundo diseño carezca de SRP.

Guven
fuente
1
¿Patrón de responsabilidad única? Principio, tal vez?
Yannis
Sí, por supuesto :) ¡Lo corrigió!
Guven
0

Me parece que el primer ejemplo está "más cerca" del verdadero SRP; Es responsabilidad del Controlador en su caso saber cómo conectar las cosas y cómo pasar el ViewModel.

¿No tendría más sentido que todo el IsValid / ValidationMessages sea parte del ViewModel? No he incursionado en MVVM, pero parece que el viejo patrón Modelo-Vista-Presentador, donde estaba bien que el Presentador manejara cosas así porque estaba directamente relacionado con la presentación. Si su ViewModel puede comprobar su validez, no hay posibilidad de pasar una no válida al método Create del UserService.

Wayne Molina
fuente
Siempre pensé que ViewModels debería ser DTO simple sin demasiada lógica en ellos. No estoy seguro de si debería poner algo como verificar la base de datos en un ViewModel ...
Kristof Claes
Supongo que dependerá de lo que implique su validación; si ViewModel solo expone el IsValidbooleano y la ValidationMessagesmatriz, aún podría llamar a una clase Validator y no tener que preocuparse por cómo se implementa la validación. El Controlador podría comprobar eso primero, por ejemplo, if (userViewModel.IsValid) { userService.Create(userViewModel); }Básicamente, ViewModel debería saber si es válido, pero no cómo sabe que es válido.
Wayne Molina