- PVSM.RU - https://www.pvsm.ru -
Обратите внимание, что хотя пост написан от первого лица, это перевод статьи из блога Jimmy Bogard, автора AutoMapper.
Меня часто спрашивают, особенно в контексте архитектуры вертикальных слоев (vertical slice architecture), где должна происходить валидация? Если вы применяете DDD, вы можете поместить валидацию внутри сущностей. Но лично я считаю, что валидация не очень вписывается в ответственность сущности.
Часто валидация внутри сущностей делается с помощью аннотаций. Допустим, у нас есть Customer и его поля FirstName/LastName обязательны:
public class Customer
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Проблем с таким подходом две:
И хотя вы можете показать ошибки валидации (обычно генерируемые ORM) пользователю, не так-то просто сопоставить исходные намерения и детали реализации состояния. Как правило, я стараюсь избегать такого подхода.
Однако, если вы придерживаетесь DDD, вы можете обойти проблему изменяющегося состояния, добавив метод:
public class Customer
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public void ChangeName(string firstName, string lastName) {
if (firstName == null)
throw new ArgumentNullException(nameof(firstName));
if (lastName == null)
throw new ArgumentNullException(nameof(lastName));
FirstName = firstName;
LastName = lastName;
}
}
Немного лучше, но лишь немного, потому что исключения — единственный способ показать ошибки валидации. Исключения вы не любите, поэтому берете какой-нибудь вариант результата [выполнения] команды (command result):
public class Customer
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public CommandResult ChangeName(ChangeNameCommand command) {
if (command.FirstName == null)
return CommandResult.Fail("First name cannot be empty.");
if (lastName == null)
return CommandResult.Fail("Last name cannot be empty.");
FirstName = command.FirstName;
LastName = command.LastName;
return CommandResult.Success;
}
}
И опять отображение ошибки пользователю вызывает раздражение, так как возвращается только одна ошибка за раз. Я мог бы вернуть их всем скопом, но как тогда мне сопоставить их с именами полей на экране? Никак. Очевидно, сущности хреново подходят для валидации команды. Однако, для этого прекрасно подходят фреймворки валидации (validation frameworks).
Вместо перекладывания валидации команды на сущность/агрегат, я полностью полагаюсь на инварианты. Вся суть инвариантов в уверенности, что я могу перейти из одного состояния в другое целиком и полностью, а не частично. То есть, фактически, это не про валидацию запроса, а про выполнение перехода между состояниями.
При таком подходе моя валидация строится вокруг команд и действий, а не сущностей. Я мог бы сделать что-то типа такого:
public class ChangeNameCommand {
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class Customer
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public void ChangeName(ChangeNameCommand command) {
FirstName = command.FirstName;
LastName = command.LastName;
}
}
Мои атрибуты валидации находятся в самой команде, и только при условии валидности команды я смогу применить ее к моим сущностям для перевода их в новое состояние. Внутри сущности я должен просто обработать команду ChangeNameCommand и выполнить переход в новое состояние, будучи уверенным, что выполняются мои инварианты. Во многих проектах я использую FluentValidation:
public class ChangeNameCommand {
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class ChangeNameValidator : AbstractValidator<ChangeNameCommand> {
public ChangeNameValidator() {
RuleFor(m => m.FirstName).NotNull().Length(3, 50);
RuleFor(m => m.LastName).NotNull().Length(3, 50);
}
}
public class Customer
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public void ChangeName(ChangeNameCommand command) {
FirstName = command.FirstName;
LastName = command.LastName;
}
}
Ключевое отличие здесь в том, что я валидирую команду, а не сущность. Сущности сами по себе — не библиотеки для валидации, так что гораздо более правильно (much cleaner) делать валидацию на уровне команд. При этом ошибки валидации прекрасно коррелируют с интерфейсом, так как именно вокруг команды в первую очередь и строился этот интерфейс.
Валидируйте команды, а не сущности, и выполняйте валидацию на границах (perform the validation at the edges).
Автор: m_a_d
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/119818
Ссылки в тексте:
[1] Источник: https://habrahabr.ru/post/282878/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.