В CQRS, как вы строите ответ при создании сущности?
Если используется CQRS и создается сущность, а значения некоторых ее свойств генерируются частью ее конструктора (например, по умолчанию active
значение для status
свойство или текущая дата / время для createdAt
), как вы включаете это в свой ответ, если ваши обработчики команд не могут возвращать значения?
4 ответа
Вам нужно создать guid перед созданием объекта, а затем использовать этот guid для запроса. Таким образом, ваши обработчики команд всегда возвращают void.
[HttpPost]
public ActionResult Add(string name)
{
Guid guid = Guid.NewGuid();
_bus.Send(new CreateInventoryItem(guid, name));
return RedirectToAction("Item", new { id = guid});
}
public ActionResult Item(Guid id)
{
ViewData.Model = _readmodel.GetInventoryItemDetailsByGuid(id);
return View();
}
Строго говоря, я не верю, что в CQRS есть точное и жесткое правило о том, что обработчики команд не возвращают значения. Грег Янг даже упоминает Мартина Фаулера stack.pop()
анекдот как верный контрпример к правилу.
CQS - разделение командных запросов, на котором основан CQRS - у Бертрана Мейера есть это правило, но оно имеет место в другом контексте и имеет исключения, одно из которых может быть интересно для рассматриваемого вопроса.
Причины CQS относительно объектов и виды инструкций, которые может дать им подпрограмма (контекст выполнения). При вводе команды возвращаемое значение не требуется, поскольку подпрограмма уже имеет ссылку на объект и может запросить его в любое время, когда она захочет выполнить эту команду.
Тем не менее, Мейер в CQS делает важное различие между командой, отправляемой известному существующему объекту, и инструкцией, которая создает объект и возвращает его.
Функции, которые создают объекты
Перед тем, как мы рассмотрим дальнейшие последствия принципа разделения команд и запросов, необходимо уточнить технический вопрос: следует ли рассматривать создание объектов как побочный эффект?
Ответ - да, как мы видели, если целью создания является атрибут a: в этом случае инструкция!! a изменяет значение поля объекта. Ответ - нет, если целью является локальная сущность процедуры. Но что, если цель является результатом самой функции, как в!! Результат или более общая форма!! Result.make (...)?
Такая инструкция по созданию не должна рассматриваться как побочный эффект. Он не изменяет любой существующий объект и, следовательно, не подвергает опасности ссылочную прозрачность (по крайней мере, если мы предположим, что памяти достаточно для размещения всех необходимых нам объектов). С математической точки зрения мы можем притворяться, что все представляющие интерес объекты на все времена прошлого, настоящего и будущего уже вписаны в Великую Книгу Объектов; инструкция создания - это просто способ получить одну из них, но она сама по себе ничего не меняет в среде. Для функции является обычным и законнымсоздание, инициализация и возврат такого объекта.
(в Построении объектно-ориентированного программного обеспечения, стр.754)
В других местах книги Мейер определяет такие функции как функции создателя.
Поскольку CQRS является расширением CQS и поддерживает точку зрения, что [Команды и Запросы] должны быть чистыми, я бы сказал, что исключения, которые имеют место для CQS, также верны в CQRS.
Кроме того, одним из основных различий между CQS и CQRS является преобразование команд и запросов в собственные объекты. В CQRS есть дополнительный уровень косвенности, "рутина" не имеет прямой ссылки на объект домена. Поиск и модификация объекта делегируются обработчику команд. Это ослабляет, IMO, одну из первоначальных причин, по которой принцип "Команды ничего не возвращают" возможен, потому что контекст теперь не может самостоятельно проверять результат операции - он в основном остается высоким и сухим, пока какой-то другой объект не позволяет ему знать о результате.
Некоторые идеи:
- Пусть ваши обработчики команд возвращают значения. Это самый простой вариант - просто вернуть то, что было создано внутри сущности. Однако есть некоторые разногласия по поводу того, разрешено ли это в CQRS.
- Предпочтительным подходом является создание значений по умолчанию (ieid) и передача их в вашу команду - например, https://github.com/gregoryyoung/m-r/blob/master/CQRSGui/Controllers/HomeController.cs в методе Add, Guid создается и передается команде CreateInventoryItem - это может быть возвращено в ответе. Это может быть довольно уродливо, если у вас есть много вещей, чтобы передать, хотя.
- Если вы не можете сделать 1 или 2, вы можете попробовать использовать какой-то асинхронный способ обработки этого, но вы еще не сказали, каков ваш вариант использования, поэтому советовать сложно. Если вы используете какую-то технологию сокетов, вы можете использовать стиль sync-over-async, где вы сразу же возвращаетесь, а затем возвращаете какое-то значение клиенту после создания объекта. Вы также можете иметь какой-то рабочий процесс, в котором вы принимаете команду, а затем опрашиваете / запрашивает созданную вещь.
В итоге я создал третий тип: CommandQuery. По возможности все помещается либо в команду, либо в запрос, но затем, когда у вас есть сценарий, в котором выполнение команды приводит к получению данных, которые вам нужны выше, просто обратитесь к CommandQuery. Таким образом, вы знаете, что это особые обстоятельства, например, вам нужен автоматический идентификатор из создания или вам нужно что-то вернуть из всплывающего окна стека, и у вас есть четкий / простой способ справиться с этим без дополнительных накладных расходов, которые создают некоторые случайные фиктивный guid или полагаться на события (сложно, когда вы находитесь в контексте веб-запроса), может вызвать. В деловой обстановке вы могли бы обсудить в команде, действительно ли CommandQuery оправдан для данной ситуации.
Согласно моему пониманию CQRS, вы не можете запрашивать агрегат, а обработчик команд не может возвращать никакого значения. Единственный разрешенный способ объединения агрегатов - прослушивание вызванных событий. Это можно сделать, просто запросив модель чтения, если изменения отражаются синхронно от событий к модели чтения.
В случае, если изменения в модели чтения являются асинхронными, все усложняется, но решения существуют.
Примечание: "обработчик команд" в моем ответе - это метод Aggregate, а не какая-то служба прикладного уровня.