Я использую CQRS. Везде, где я читал, мне говорят, что логика проверки должна быть помещена в объекты команд. Например, см. Эту ссылку: https://lostechies.com/jimmybogard/2016/04/29/validation-inside-or-outside-entities/
См. Команду ниже (взято из ссылки):
public class ChangeNameCommand {
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
и бизнес-объект ниже (также взят из ссылки - обратите внимание, что я изменил параметр, переданный конструктору Customer, с класса на интерфейс):
public class Customer
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public void ChangeName(IChangeNameCommand command) {
FirstName = command.FirstName;
LastName = command.LastName;
}
}
В моем случае команды хранятся в одной библиотеке классов, а бизнес-объекты - в других (поскольку команды используются в нескольких проектах типа микросервисов). Если я следую инструкциям (и добавляю проверку в команды), я считаю, что ничто не может помешать разработчику сделать это:
public class ChangeNameCommandWithoutValidation : IChangeNameCommand {
public string FirstName { get; set; }
public string LastName { get; set; }
}
а затем передать команду (без проверки) объекту домена. В этом случае я считаю, что объект домена не контролирует, что ему передается?
Поэтому должен ли я идти против всех рекомендаций, которые я могу найти, и провести проверку в доменном объекте? Я считаю, что должен сделать это, потому что команды находятся в отдельной библиотеке классов для объектов домена. Правильно ли я это понял?
Я считаю, что этот вопрос также актуален при передаче события объекту домена клиента (при использовании источника событий).
@kayess, означает ли это, что класс модели предметной области может быть недопустимым (если разработчик забыл или случайно удалил проверку из команды)?
Я так не думаю, у вас должны быть единичные / интеграционные / какие-либо другие тесты для вашей SUT, которые будут нести ответственность за то, чтобы такие несчастные случаи не могли произойти. Но, как гласит моя любимая цитата по этому поводу: «нет защиты от чуши человеческой». Также вы можете выполнять валидацию в стиле АОП, например иметь декоратор ... Другой способ подумать об этом, вы можете проводить обзоры кода и т.д., но это выходит из-под контроля, поскольку мы становимся слишком широкими.
Я не эксперт в CQRS, но мне кажется странным, что вы используете интерфейс для команд. Есть ли вообще смысл иметь несколько реализаций для одного командного интерфейса? Думаю, ваш пример с ChangeNameCommandWithoutValidation показывает, что это не так. И менее "зло" пример был бы IChangeNameCommand со вторым именем. Ваш объект Customer примет такую команду, но на самом деле не может ее обработать.
Что касается вашего последнего редактирования, вы не передаете событие объекту домена. Вы передаете (как и делаете) команды или другие параметры объекту домена, говоря им, что нужно что-то сделать. Ваш объект домена или обработчик команд вызывает события, которые обрабатываются обработчиками событий ... Разница в том, что команды = сейчас, события = что-то произошло, потому что было какое-то взаимодействие с логикой домена.
@kayess, спасибо. Если используются события, должна ли проверка по-прежнему выполняться в команде? Должен ли класс команды всегда находиться в том же классе, что и объект домена?
Это дело вкуса. Но вам не нужно проверять события, поскольку они являются результатом логики вашего домена, которая (должна быть) защищена ранее проверенной проверкой. События - это просто результаты некоторой уже произошедшей логики, сообщающей подписчикам, что произошло это. По крайней мере, так я это вижу, оставьте это здесь, чтобы посмотреть, как поступят другие.
@kayess, вы говорите, что проверка должна быть продублирована в модели команды и домена?
Нет. Я говорю, что вы должны проверять все входящие данные. Где вы это сделаете, действительно зависит от вас, будь то начальная проверка в обработчике команд и некоторая проверка, ориентированная на домен, в объекте домена. Никто не будет заставлять вас делать это так или иначе ... :) И эти блоги берите как примеры, а не высеченные на камне правила.
@kayess, я считаю, что проверка должна выполняться на границах, и, похоже, есть две границы, т.е. по команде и на мероприятии. Я стараюсь следовать принципу наименьшего удивления.
Позвольте нам продолжить обсуждение в чате.
@kayess, я задал следующий вопрос, если вы хотите взглянуть: softwareengineering.stackexchange.com/questions/372338/….





Несколько мыслей:
Сообщение в блоге, на которое вы ссылаетесь, сбивает с толку, потому что оно приравнивает валидацию к инвариантам.
В литературе по DDD инварианты чаще всего относятся к правилам домена, которые применяются в корневом объекте и нигде больше. Что касается действительности домен, не «все указания», как вы выразились, предназначены для обеспечения их выполнения на стороне команды - как раз наоборот. Популярные школы мысли считают, что объект всегда должен быть действительным и, следовательно, должен заботиться о своих собственных правилах (так называемых инвариантах).
С другой стороны, вид валидности, о котором говорят образцы в сообщении Джимми Богарда, находится на границе между валидностью домена и валидностью пользовательского ввода. Я бы не стал твердым правилом ставить такую проверку на ту или иную сторону. Хотя правомерно считать, что это действительно работа для команды, с некоторыми системами типов вы можете идеально кодировать такого рода ненулевые ограничения в типах свойств сущности, и было бы стыдно не воспользоваться этой бесплатной дополнительной корректностью.
Как было сказано в комментариях, размещение интерфейса поверх команды кажется странным.
Думать о том, что может случиться, если кто-то злонамеренно разделит команду на подклассы, вероятно, бессмысленно защищать. Внутри команды у вас есть полный контроль над тем, что реализовано, и я не могу придумать веской причины для «командных программистов» быть в другой команде, чем «программисты сущностей».
Спасибо. Я полагаю, вы говорите, что объект действителен, если соблюдаются все его инварианты. Я полагаю, вы также говорите, что сущность может быть действительной с такими данными: ID = Guid.Empty, Name = "", Age = 0 и т. д. Верно?
Как я уже сказал, действительный - это загруженный термин. Для определения валидности, ориентированного на предметную область, да. Для определения, ориентированного на ввод пользователя, нет.
Я только что нашел здесь ваш ответ: stackoverflow.com/questions/30190302/…. Мне нравится комментарий, в котором говорится: «вы используете проверку, чтобы убедиться, что входные данные имеют допустимый формат, а затем бизнес-правила решают, как / если входные данные изменяют модель». Я интерпретирую это как означающее, что проверка выполняется командой, а инварианты рассматриваются в модели предметной области. +1 за другой ответ.
Например, допустим, у клиента есть свойство Customer.Offer. Предложение создается, если Клиент старше 21 года и потратил 1000 фунтов стерлингов. В этом случае проверка (в команде) будет проверять, что возраст больше или равен 0 и что расходы больше или равны 0, а модель предметной области будет проверять, что клиент старше 21 года и потратил 1000 фунтов стерлингов. или более перед назначением оферты. Это правильно?
Я думаю, пока вы относитесь к ним как к примерам, а не к каменным правилам вроде «> 0 всегда означает проверку команды вне зависимости от контекста» ;-)
Опять же, я говорю, что, поскольку с точки зрения строго типизированного функционального программирования для экземпляра, вы получаете гораздо больше этой проверки, запеченной в системе типов и, как следствие, запеченной в сущностях, так что различие становится недействительным.
Прежде чем я отвечу и проведу черту под этим - «> 0 всегда означает проверку команды вне зависимости от контекста». Если есть контекст, то я считаю, что он будет специфичным для предметной области и должен появиться в модели предметной области?
Это могло быть как угодно. В зависимости от контекста есть домен> 0 и аппликативный> 0. Используйте здравый смысл, чтобы принять решение;)
Я задал следующий вопрос, если вы хотите взглянуть: softwareengineering.stackexchange.com/questions/372338/…
Я думаю, что вам мощь лучше иметь обработчик команд, обрабатывающий конкретную команду. Затем вы можете выполнить проверку команды там и после этого вызвать агрегат или объект для выполнения логики домена. Таким образом, у вас может быть красивый и понятный способ использования. Я не сторонник передачи команд прямо в агрегаты, поскольку они должны воздействовать на самих себя и на свои соответствующие объекты, которых они должны защищать, и так далее. Но в целом, здесь нет серебряной пули, делайте то, что ваша команда / партнеры решают и могут работать / решать.