Когда я следую SRP, как мне поступить с проверкой и сохранением сущностей?

10

В последнее время я читал « Чистый код» и различные онлайн-статьи о SOLID, и чем больше я читаю об этом, тем больше чувствую, что ничего не знаю.

Допустим, я создаю веб-приложение с использованием ASP.NET MVC 3. Допустим, у меня есть действие UsersControllerс таким Createдействием:

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

    }
}

В этом методе действия я хочу сохранить пользователя в базе данных, если введенные данные верны.

Теперь, согласно принципу единой ответственности, объект должен нести единственную ответственность, и эта ответственность должна быть полностью заключена в класс. Все его услуги должны быть тесно связаны с этой ответственностью. Поскольку проверка и сохранение в базе данных являются двумя отдельными обязанностями, я думаю, мне следует создать отдельный класс для их обработки следующим образом:

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

Это имеет некоторый смысл для меня, но я совсем не уверен, что это правильный способ справиться с такими вещами. Это, например , вполне возможно передать недопустимый экземпляр CreateUserViewModelв IUserServiceклассе. Я знаю, что могу использовать встроенные аннотации данных, но что, если их недостаточно? Изображение, которое my ICreateUserValidatorпроверяет базу данных, чтобы увидеть, есть ли уже другой пользователь с таким же именем ...

Другой вариант - позволить IUserServiceпроверке пройти проверку следующим образом:

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

Но я чувствую, что нарушаю принцип единой ответственности здесь.

Как я должен иметь дело с чем-то вроде этого?

Кристоф Клас
источник
Разве userкласс не должен обрабатывать проверку? SRP или нет, я не понимаю, почему userэкземпляр не должен знать, когда он действителен или нет, и полагаться на что-то другое, чтобы определить это для него. Какие другие обязанности у класса? Кроме того, при userизменении валидация, вероятно, изменится, поэтому передача на аутсорсинг другому классу создаст только тесно связанный класс.
Себастьян

Ответы:

4

Я действительно не думаю, что вы нарушаете принцип единой ответственности во втором примере.

  • У UserServiceкласса есть только одна причина для изменения: если есть необходимость изменить способ сохранения пользователя.

  • У ICreateUserValidatorкласса есть только одна причина для изменения: если есть необходимость изменить способ проверки пользователя.

Я должен признать, что ваша первая реализация более интуитивна. Однако проверка должна выполняться объектом, который создает пользователя. Сам создатель не должен нести ответственность за проверку; он должен скорее делегировать ответственность классу валидатора (как в вашей второй реализации). Так что я не думаю, что второй дизайн не имеет SRP.

Guven
источник
1
Single-Ответственность-шаблон? Принцип, возможно?
Яннис
Да, конечно :) Исправил это!
Guven
0

Мне кажется, что первый пример «ближе» к истинному SRP; В вашем случае контроллер обязан знать, как соединить вещи и как передать ViewModel.

Разве не имеет смысла, чтобы все сообщения IsValid / ValidationMessages были частью самой ViewModel? Я не баловался с MVVM, но похоже на старый шаблон Model-View-Presenter, где Presenter мог нормально обрабатывать такие вещи, потому что он был напрямую связан с презентацией. Если ваша ViewModel может проверить себя на достоверность, нет возможности передать неверный метод в метод UserService Create.

Уэйн Молина
источник
Я всегда думал, что ViewModels должны быть простыми DTO без лишней логики в них. Я не уверен, стоит ли ставить что-то вроде проверки базы данных в ViewModel ...
Кристоф Клэйс,
Я предполагаю, что это будет зависеть от того, что влечет за собой ваша проверка; если ViewModel просто предоставляет IsValidлогическое значение и ValidationMessagesмассив, он все равно может вызывать класс Validator и не должен беспокоиться о том, как выполняется проверка. Контроллер может проверить , что первый , например , В if (userViewModel.IsValid) { userService.Create(userViewModel); }основном ViewModel должны знать , если это действительно, но не как он знает , что это действует.
Уэйн Молина