Quando seguo SRP, come devo gestire la convalida e il salvataggio di entità?


10

Recentemente ho letto Clean Code e vari articoli online su SOLID, e più ne leggo, più mi sento come se non sapessi nulla.

Diciamo che sto costruendo un'applicazione web usando ASP.NET MVC 3. Diciamo che ho un UsersControllercon Createun'azione come questa:

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

    }
}

In quel metodo di azione voglio salvare un utente nel database se i dati inseriti sono validi.

Ora, secondo il principio di responsabilità singola, un oggetto dovrebbe avere un'unica responsabilità e tale responsabilità dovrebbe essere interamente incapsulata dalla classe. Tutti i suoi servizi dovrebbero essere strettamente allineati a tale responsabilità. Poiché la convalida e il salvataggio nel database sono due responsabilità distinte, immagino che dovrei creare una classe separata per gestirle in questo modo:

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

Questo ha un senso per me, ma non sono affatto sicuro che questo sia il modo giusto di gestire cose come questa. Ad esempio, è del tutto possibile passare un'istanza non valida CreateUserViewModelalla IUserServiceclasse. So che potrei usare DataAnnotations integrato, ma cosa succede quando non sono sufficienti? Immagine che il mio ICreateUserValidatorcontrolla nel database per vedere se esiste già un altro utente con lo stesso nome ...

Un'altra opzione è lasciare che IUserServicesi occupi della convalida in questo modo:

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

Ma sento che sto violando il principio di responsabilità singola qui.

Come dovrei affrontare qualcosa del genere?


La userclasse non dovrebbe gestire la validazione? SRP o no, non vedo perché l' useristanza non dovrebbe sapere quando è valida o meno e fare affidamento su qualcos'altro per determinarlo. Quali altre responsabilità ha la classe? Inoltre, quando le usermodifiche cambiano, la convalida probabilmente cambierà, quindi l'outsourcing a una classe diversa creerà solo una classe strettamente accoppiata.
sebastiangeiger,

Risposte:


4

Non credo davvero che tu stia violando il principio di responsabilità singola nel tuo secondo esempio.

  • La UserServiceclasse ha solo un motivo per cambiare: se è necessario cambiare il modo in cui si salva un utente.

  • La ICreateUserValidatorclasse ha solo un motivo per cambiare: se è necessario cambiare il modo in cui convalidi un utente.

Devo ammettere che la tua prima implementazione è più intuitiva. Tuttavia, la convalida deve essere eseguita dall'entità che crea l'utente. Il creatore stesso non dovrebbe essere responsabile della convalida; dovrebbe piuttosto delegare la responsabilità a una classe validatrice (come nella tua seconda implementazione). Quindi, non credo che il secondo design manchi di SRP.


1
Single-Responsabilità-modello? Principio, forse?
yannis,

Sì, certo :) Corretto!
Guven,

0

Mi sembra che il primo esempio sia "più vicino" al vero SRP; è responsabilità del Controller nel tuo caso sapere come collegare le cose e come passare lungo il ViewModel.

Non avrebbe più senso che l'intero IsValid / ValidationMessages faccia parte del ViewModel stesso? Non mi sono dilettato con MVVM, ma sembra il vecchio modello Model-View-Presenter, in cui per il Presenter andava bene gestire cose del genere perché era direttamente correlato alla presentazione. Se ViewModel è in grado di verificarne la validità, non è possibile passarne uno non valido al metodo Create di UserService.


Ho sempre pensato che i ViewModel fossero semplici DTO senza troppa logica. Non sono sicuro se dovrei mettere qualcosa come controllare il database in un ViewModel ...
Kristof Claes,

Immagino che dipenda da cosa comporta la tua validazione; se ViewModel espone solo il IsValidbooleano e l' ValidationMessagesarray, potrebbe comunque chiamare in una classe Validator e non doversi preoccupare di come viene implementata la convalida. Il controller potrebbe verificare che, ad esempio, if (userViewModel.IsValid) { userService.Create(userViewModel); }fondamentalmente ViewModel dovrebbe sapere se è valido, ma non come sa che sia valido.
Wayne Molina,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.