CQRS - Когда отправлять подтверждение?
Пример: В бизнес-правилах указывается, что клиент должен получить сообщение подтверждения (по электронной почте или подобное) при размещении заказа.
Давайте скажем, что a NewOrderRegisteredEvent
отправляется из домена и подбирается прослушивателем событий, который отправляет сообщение подтверждения. Когда это делается, какой-то другой обработчик событий выдает исключение или что-то еще идет не так, а часть работы откатывается назад. Теперь мы отправили пользователю сообщение о подтверждении того, что было отброшено.
Что такое "cqrs" способ решения таких проблем, когда вы хотите что-то сделать после того, как часть работы была совершена? Другим осложняющим фактором является переименование событий. Я не хочу, чтобы старые подтверждающие сообщения отправлялись повторно, всякий раз, когда я воспроизводил записанные события, чтобы создать новый вид/проекцию.
Моя лучшая теория: я только начал изучать увлекательный мир cqrs и задавался вопросом, будет ли это что-то, что будет реализовано как сага? Если сага похожа на конечный автомат, где каждый переход возможен только один раз, то я думаю, что это решило бы эту проблему? Мне просто сложно понять, как это будет соответствовать командной шине и событиям домена.
Ответы
Ответ 1
-
Событие должно появляться только после завершения транзакции. Если что-то пойдет не так, и там откат, то событие не произошло с внешней точки зрения. Поэтому он не должен публиковаться вообще. Хотя при необходимости может быть опубликовано событие OrderRegistrationFailed
.
-
Вы не хотите, чтобы почта отправлялась, если команда не была успешно выполнена.
Сначала несколько причин, по которым обработчик команд, как предлагалось в другом ответе, были бы неправильным местом: в некоторых случаях обработчик команд не смог бы определить, удастся ли команде в конечном итоге или нет. Когда обработчик команды вызовет отправку почты, также будет передаваться знание процесса внутри обработчика команд, что приведет к поломке SRM и слишком жесткому сопряжению бизнес-правил с прикладным уровнем.
Почта должна быть отправлена после факта, то есть из обработчика события.
Чтобы предотвратить запуск этого обработчика во время воспроизведения, вы можете просто его не зарегистрировать. Это похоже на то, как вы тестируете свое приложение. Вы регистрируете только тех обработчиков, которые вам действительно нужны.
- Производственная система → зарегистрировать все обработчики событий
- Тесты → регистрируют только обработчики обработанных событий
- Replay → зарегистрировать только обработчики проекции/денормализации
Другое - еще более слабо связанное, хотя и более сложное - возможность иметь Saga
дескриптор NewOrderRegisteredEvent
и выдавать команду SendMail
в соответствующий ограниченный контекст (спасибо, Yves Reynhout, для указания это в комментариях к вопросу).
Ответ 2
Есть два возможных решения
1) Публикация события и обработка события (т.е. письмо) являются частью одной транзакции. В этом случае ваша структура транзакций позаботится об этом для вас. Если сообщение не удается, событие отменяется. Вероятно, вы попробуете повторить команду. Это концептуально чисто и легко думать. Никакое событие не заканчивается, пока все, у кого есть что сказать об этом, не сказали. Однако, практически говоря, это может быть болезненным, поскольку оно обычно связано с распределенными транзакциями. Их трудно найти. Может ли ваш почтовый клиент регистрироваться в той же транзакции, что и база данных, в которой хранятся ваши события?
2) Публикация события является транзакционным, но обработчики событий обрабатывают транзакции по-своему. Обработчик событий, отправляющий электронные письма, может отслеживать, какие события он видел. Если он разбился, он будет запрашивать старые события и обрабатывать их. Вы могли бы принять бизнес-решение относительно того, насколько велика сделка, если бы у людей отсутствовали или дублировались электронные письма. (Для транзакций, связанных с деньгами, ответ, вероятно, вы не должны этого допускать.)
Решение (2) обычно является тем, что вы видите в кругах DDD/CQRS, поскольку это более слабо связанное решение. Решение (1) вполне практично в небольшой системе, где хранилище событий и прогнозы находятся в одной базе данных, а прогнозы не часто меняются. Решение (2) позволяет разным обработчикам событий работать по-своему. Решение (1) может привести к тому, что многие проблемы, не связанные с перекрытием, будут запутаны. В этом случае ваши бизнес-правила вашего заказа не завершаются до тех пор, пока не позаботятся многие причудливые вещи, которые происходят при отправке по электронной почте. Во-первых, это может немного замедлить вас.
Если отправка электронной почты была более интересной, чем "увиденное событие, отправленное письмо", то вы правы, у вас может быть сага или рабочий процесс на ваших руках. Электронная почта в больших операциях часто представляет собой сложную систему, которая вам вряд ли понадобится реализовать. Вам просто нужно быть уверенным, что вы поместили свою электронную почту в какую-либо очередь запросов (используя подход (2)), и система электронной почты, скорее всего, сделает повторные попытки/пакетное/спам-уклонение/работу за ночь/и т.д.