Проверка домена в архитектуре CQRS

Опасно... Опасно, доктор Смит... Впереди философский пост

Цель этого поста — определить, действительно ли размещение логики проверки за пределами объектов домена (совокупный корень) предоставляет мне больше гибкости или это код камикадзе

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

Первый подход, который я рассматривал, был следующим:

class Customer : EntityBase<Customer>
{
   public void ChangeEmail(string email)
   {
      if(string.IsNullOrWhitespace(email))   throw new DomainException(“...”);
      if(!email.IsEmail())  throw new DomainException();
      if(email.Contains(“@mailinator.com”))  throw new DomainException();
   }
}

На самом деле мне не нравится эта проверка, потому что даже когда я инкапсулирую логику проверки в правильную сущность, это нарушает принцип Open/Close (открыть для расширения, но закрыть для модификации), и я обнаружил, что нарушение этого принципа, обслуживание кода становится настоящей проблемой, когда приложение становится сложнее. Почему? Поскольку правила домена меняются чаще, чем нам хотелось бы признать, и если правила скрыты и встроеныв сущность, подобную этой, их трудно тестировать, трудно читать, трудно поддерживать, но настоящая причина почему мне не нравится такой подход: если правила проверки меняются, я должен прийти и отредактировать свою доменную сущность.Это был действительно простой пример, но в RL проверка может быть более сложной

. Итак, следуя философии Уди Дахана, явному определению ролейи рекомендации Эрика Эванса в синей книге, следующий попытаться было реализовать шаблон спецификации, что-то вроде этого

class EmailDomainIsAllowedSpecification : IDomainSpecification<Customer>
{
   private INotAllowedEmailDomainsResolver invalidEmailDomainsResolver;
   public bool IsSatisfiedBy(Customer customer)
   {
      return !this.invalidEmailDomainsResolver.GetInvalidEmailDomains().Contains(customer.Email);
   }
}

. Но затем я понял, что для того, чтобы следовать этому подходу, мне пришлось сначала изменить свои объекты, чтобы передать значение, которое проверяется , в этом случае электронной почты, но их изменение приведет к срабатыванию событий моего домена, чего я не хотел бы, пока новое электронное письмо не станет действительным

. Архитектура CQRS:

class EmailDomainIsAllowedValidator : IDomainInvariantValidator<Customer, ChangeEmailCommand>
{
   public void IsValid(Customer entity, ChangeEmailCommand command)
   {
      if(!command.Email.HasValidDomain())  throw new DomainException(“...”);
   }
}

Ну, это основная идея, сущность передается валидатору на тот случай, если нам потребуется какое-то значение от сущности для выполнения проверки, команда содержит данные, поступающие от пользователя, и поскольку валидаторы учитываются инъекционныепредметы, которые они могли внедрить внешние зависимости, если этого требует проверка.

Теперь дилемма, я доволен таким дизайном, потому что моя проверка инкапсулирована в отдельные объекты, что дает много преимуществ: простота модульного тестирования, простота обслуживания, доменные инварианты явно выражены с использованием Ubiquitous Language, простота для расширения логика проверки централизована, и валидаторы могут использоваться вместе для обеспечения соблюдения сложных правил предметной области.И даже когда я знаю, что размещаю проверку своих сущностей за их пределами (Можно утверждать, что это запах кода — Анемический Домен), но я думаю, что компромисс приемлем

Но есть одна вещь, которую я не понял. как реализовать это в чистом виде. Как мне использовать эти компоненты...

Поскольку они будут внедрены, они не будут естественным образом вписываться в мои доменные объекты, поэтому в основном я вижу два варианта:

  1. Передать валидаторы каждому методу моего сущность

  2. Проверить мои объекты извне (из обработчика команд)

Меня не устраивает вариант 1, поэтому я объясню, как бы я сделал это с вариантом 2

class ChangeEmailCommandHandler : ICommandHandler<ChangeEmailCommand>
{
   // here I would get the validators required for this command injected
   private IEnumerable<IDomainInvariantValidator> validators;
   public void Execute(ChangeEmailCommand command)
   {
      using (var t = this.unitOfWork.BeginTransaction())
      {
         var customer = this.unitOfWork.Get<Customer>(command.CustomerId);
         // here I would validate them, something like this
         this.validators.ForEach(x =. x.IsValid(customer, command));
         // here I know the command is valid
         // the call to ChangeEmail will fire domain events as needed
         customer.ChangeEmail(command.Email);
         t.Commit();
      }
   }
}

Ну вот и все. Можете ли вы поделиться своими мыслями по этому поводу или поделиться своим опытом проверки объектов домена

РЕДАКТИРОВАТЬ

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

Я знаю, что размещение проверки вне моих объектов нарушает инкапсуляцию, как упоминал @jgauffin в своем ответе, но я думаю, что преимущества размещения проверки в отдельных объектах гораздо более существенны, чем просто сохранение инкапсуляции объекта.Теперь я думаю, что инкапсуляция имеет больше смысла в традиционной n-уровневой архитектуре, потому что сущности использовались в нескольких местах доменного уровня, но в архитектуре CQRS, когда поступает команда, будет обработчик команды, обращающийся к совокупному корню и выполнение операций с совокупным корнем только создает идеальное окно для размещения проверки.

Я хотел бы провести небольшое сравнение между преимуществами размещения проверки внутри объекта по сравнению с ее размещением в отдельных объектах

  • Проверка в отдельных объектах

    • Pro. Легко писать
    • Pro. Легко проверить
    • Pro.Это явно выражено
    • Pro. Он становится частью дизайна домена, выраженного с помощью текущего Ubiquitous Language
    • Pro. Поскольку теперь это часть проекта, его можно смоделировать с помощью диаграмм UML
    • Pro. Чрезвычайно прост в обслуживании
    • Pro. Делает мои объекты и логику проверки слабо связанными
    • Pro. Простота расширения
    • Pro. Следуя SRP
    • Pro. Следуя принципу Open/Close
    • Pro. Не нарушая закон Деметры (ммм)?
    • Про. Я централизован
    • Pro. Он может быть многоразовым
    • Pro. При необходимости можно легко внедрить внешние зависимости
    • Pro. При использовании подключаемой модели новые валидаторы можно добавлять, просто удаляя новые сборки без необходимости повторной компиляции всего приложения
    • Pro. Внедрение механизма правил было бы проще
    • Con. Нарушение инкапсуляции
    • Кон. Если инкапсуляция является обязательной, нам придется передать отдельные валидаторы методу объекта (агрегата)
  • Validation, инкапсулированному внутри объекта

    • Pro. Инкапсулированный?
    • Про. Многоразовый?

Хотелось бы прочитать ваши мысли по этому поводу

45
задан Jupaol 4 June 2012 в 21:56
поделиться