Почему команды и события представлены отдельно?
В чем разница между командами и событиями в архитектурах, которые подчеркивают события? Единственное различие, которое я вижу, состоит в том, что команды обычно создаются / вызываются субъектами вне системы, в то время как события, похоже, создаются обработчиками и другим кодом в системе. Однако во многих примерах приложений, которые я видел, они имеют разные (но функционально похожие) интерфейсы.
11 ответов
Команды могут быть отклонены.
События произошли.
Это, наверное, самая важная причина. В архитектуре, управляемой событиями, не может быть никаких сомнений в том, что возникшее событие представляет собой нечто, что произошло.
Теперь, поскольку команды - это то, что мы хотим, а события - это то, что произошло, мы должны использовать разные глаголы, когда мы называем эти вещи. Это приводит к отдельным представлениям.
Я вижу, что команды обычно создаются / вызываются субъектами вне системы, в то время как события, похоже, создаются обработчиками и другим кодом в системе.
Это еще одна причина, по которой они представлены отдельно. Концептуальная ясность.
Команды и события являются сообщениями. Но на самом деле это отдельные понятия, и понятия должны быть смоделированы в явном виде.
Событие является фактом из прошлого.
Команда только запрос, и, таким образом, может быть отказано.
Важной характеристикой команды является то, что она должна быть обработана только один раз одним получателем. Это связано с тем, что команда - это отдельное действие или транзакция, которые вы хотите выполнить в приложении. Например, одна и та же команда создания заказа не должна обрабатываться более одного раза. Это важное различие между командами и событиями. События могут обрабатываться несколько раз, поскольку в событии могут быть заинтересованы многие системы или микросервисы. 'msdn'
Кроме того, в дополнение ко всем представленным здесь ответам обработчик события может также инициировать команду после получения уведомления о том, что событие произошло.
Скажем, например, что после создания Customer вы также хотите инициализировать некоторые значения учетных записей и т. Д. После того, как ваш Customer AR добавляет событие в EventDispatcher, и оно получает объект CustomerCreatedEventHandler, этот обработчик может инициировать отправку команды, которая выполнит все что нужно и т. д.
Также есть DomainEvents и ApplicationEvents. Разница просто концептуальная. Сначала вы хотите отправить все события вашего домена (некоторые из них могут вызывать события приложения). Что я имею в виду под этим?
Инициализация учетной записи после события CustomerCreatedEvent является событием DOMAIN. Отправка клиенту уведомления по электронной почте является событием подачи заявки.
Причина, по которой вы не должны смешивать их, ясна. Если ваш SMTP-сервер временно не работает, это не означает, что это должно повлиять на вашу ОПЕРАЦИЮ ДОМЕНА. Вы все еще хотите сохранить неиспорченное состояние ваших агрегатов.
Я обычно добавляю события в мой Диспетчер на уровне совокупного корня. Это события DomainEvents или ApplicationEvents. Может быть и то, и другое. Когда мой обработчик команд завершен, и я вернулся в стек с кодом, который выполняет обработчик команд, тогда я проверяю свой диспетчер и отправляю любое другое DomainEvent. Если все это успешно, то я закрываю транзакцию.
Если у меня есть какие-либо события приложений, самое время их отправить. Отправка электронного письма не обязательно требует открытого соединения с базой данных или открытой области транзакции.
Я немного отошел от первоначального вопроса, но для вас также важно понять, как к событиям также можно относиться концептуально.
Тогда у вас есть саги.... но это WAYYYY за рамками этого вопроса:)
Имеет ли это смысл?
Проработав несколько примеров и особенно презентацию Грега Янга ( http://www.youtube.com/watch?v=JHGkaShoyNs), я пришел к выводу, что команды являются избыточными. Это просто события от вашего пользователя, они действительно нажали эту кнопку. Вы должны хранить их точно так же, как и другие события, потому что это данные, и вы не знаете, захотите ли вы использовать их в будущем. Ваш пользователь добавил, а затем удалил этот элемент из корзины или, по крайней мере, попытался. Позже вы можете использовать эту информацию, чтобы напомнить пользователю об этом позже.
Просто чтобы добавить к этим замечательным ответам. Хочу отметить отличия в отношении сцепления.
Команды направлены на конкретный процессор. Таким образом, существует некоторый уровень зависимости / связи с инициатором команды и процессором.
Например, UserService
при создании нового пользователя отправляет команду "Отправить электронное письмо" EmailService
.
Тот факт, что UserService
знает, что ему нужен EmailService
, который уже связан. ЕслиEmailService
изменяет схему API или отключается, это напрямую влияет на UserService
функция.
События не направлены на какой-либо конкретный обработчик событий. Таким образом, издатель событий становится слабосвязанным. Его не волнует, какой сервис потребляет его событие. Допустимо даже иметь 0 потребителей события.
Например, UserService
при создании нового пользователя публикует "Событие, созданное пользователем". ПотенциальноEmailService
может использовать это событие и отправить пользователю электронное письмо.
Здесь UserService
не знает EmailService
. Они полностью разделены. ЕслиEmailService
снижается или меняет бизнес-правила, нам нужно только отредактировать EmailService
Оба подхода имеют свои достоинства. Архитектурный проект, основанный исключительно на событиях, труднее отслеживать, поскольку он слишком слабо связан, особенно в больших системах. И командная тяжелая архитектура имеют высокий уровень связи. Так что хороший баланс идеален.
Надеюсь, это имеет смысл.
В дополнение к упомянутым выше концептуальным различиям, я думаю, есть еще одно отличие, связанное с общими реализациями:
События обычно обрабатываются в фоновом цикле, который должен опрашивать очереди событий. Любая сторона, заинтересованная в воздействии на событие, обычно может зарегистрировать обратный вызов, который вызывается в результате обработки очереди событий. Таким образом, событие может быть один ко многим.
Команды, возможно, не должны обрабатываться таким образом. Создатель команды обычно имеет доступ к предполагаемому исполнителю команды. Это может быть, например, в форме очереди сообщений исполнителю. Таким образом, команда предназначена для одного объекта.
Они представлены отдельно, потому что они представляют очень разные вещи. Как сказал @qstarin, команды - это сообщения, которые могут быть отклонены, и в случае успеха произойдет событие. Команды и события являются Dtos, они являются сообщениями, и они имеют тенденцию выглядеть очень похожими при создании и создании сущности, однако с тех пор не обязательно.
Если вы беспокоитесь о повторном использовании, то вы можете использовать команды и события в качестве конвертов для вашей (Messge) полезной нагрузки
class CreateSomethingCommand
{
public int CommandId {get; set;}
public SomethingEnvelope {get; set;}
}
однако, я хотел бы знать, почему вы спрашиваете:D у вас слишком много команд / событий?
Вы не можете повторно вычислить состояние на основе команд, потому что в целом они могут давать разные результаты каждый раз при обработке.
Например, представьте себе GenerateRandomNumber
команда. Каждый раз, когда он вызывается, он будет генерировать другое случайное число X. Таким образом, если ваше состояние зависит от этого числа, каждый раз, когда вы пересчитываете свое состояние из истории команд, вы будете получать другое состояние.
События решают эту проблему. Когда вы выполняете команду, она создает последовательность событий, которые представляют результат выполнения команды. Например,GenerateRandomNumber
команда могла произвести GeneratedNumber(X)
событие, регистрирующее сгенерированное случайное число. Теперь, если вы повторно вычислите свое состояние из журнала событий, вы всегда будете получать одно и то же состояние, потому что вы всегда будете использовать тот же номер, который был сгенерирован при конкретном выполнении команды.
Другими словами, команды - это функции с побочными эффектами, события записывают результат определенного выполнения команды.
Примечание. Вы по-прежнему можете записывать историю команд для целей аудита или отладки. Дело в том, что для пересчета состояния вы используете историю событий, а не историю команд.
Я думаю, что кое-что добавить к ответу Квентина-Сантина заключается в том, что они:
Инкапсулируйте запрос в виде объекта, что позволит вам параметризовать клиентов с различными запросами, ставить запросы в очередь или в журнал и поддерживать отменяемые операции.
Я хотел бы дополнить уже хорошие ответы еще одним аспектом, особенно. для тех, кто знаком с функциональным ядром, шаблон императивной оболочки .
События соотносятся с функциональным ядром, а команды с императивной оболочкой. То есть события пребывают в чистом, а команды — в нечистом. Оболочка, как и любой CLI, справляется со всеми нечистотами.
Это видно по тому, что команды проверяются и иногда отклоняются. Это также видно из того, что команды могут полагаться на рандомизацию, а события — нет. Например, бросание костей в игре в нарды предполагает выдачуroll
команда и ее результат при генерации будут немедленно увековечены вrolled
событие. Ценность этого разделения можно дополнительно увидеть в способности создавать или воспроизводить события.
Хотя живые события могут всплывать в оболочке и вызывать новые команды и новые побочные эффекты, именно по этой причине воспроизведенные события не должны всплывать.
Развивая концепции из комментария @LeviFuller, я также использовал шаблоны команд и событий для представления «ровно одного» и «0 или более» слушателей соответственно, но другим способом, чем ответ @froi, где оба все еще разделены.
В этой архитектуре издатель команды по-прежнему не знает, кто будет обрабатывать команду (как и в случае с событиями). Разница в том, что издатель уверен, что команду кто-то обработает. Таким образом, хотя вы и можете смоделировать любую команду как событие, это было бы нежелательно, поскольку у издателя нет такой гарантии.
Это становится особенно важным при рассмотрении перехода между командными процессорами. Переход может произойти во многих сценариях — например, если командный процессор является сетевой службой, ему может потребоваться отключиться для обслуживания; альтернативно, в приложении на основе пользовательского интерфейса командный процессор может представлять экран, на котором находится пользователь, и пользователь переходит на другой экран.
Во время перехода обычно требуется убедиться, что поступающие команды обрабатываются только новым процессором. Старый процессор отменит регистрацию как можно скорее, а это означает, что может быть пробел, пока ни один процессор не зарегистрирован для типа команды - и концентратор команд (аналог концентратора событий) должен поддерживать очередь команд, полученных в течение этого времени. Сюда также могут относиться частично обработанные команды того времени, поскольку старый процессор должен повторно отправить «необработанную работу» после отмены регистрации.
Ничего из этого было бы невозможно с использованием событий. Если никто не слушает событие, это означает, что оно никого не волнует, и это нормально. В этом случае события не нужно будет ставить в очередь. Если подписчик событий не завершил обработку события на момент его завершения, обычно нет необходимости повторно отправлять «необходимую работу», поскольку она больше не актуальна. События работают по принципу «выстрелил и забыл», поскольку нет никаких ожиданий того, что произойдет после их отправки, но при наличии (действительной) команды ожидается, что что-то будет сделано, даже во время перехода.