Отправка электронной почты или SMS с использованием CQRS и доменного проектирования

В настоящий момент мы строим новую архитектуру, основанную на принципах CQRS и доменного проектирования. Сейчас у нас есть некоторые дискуссии о том, как мы должны иметь дело с внешним сообщением. Чтобы сделать вопрос более конкретным, я использую пример отправки уведомления SMS, когда клиент создает заказ.

Клиент создает команду NewOrderCommand, которая обрабатывается соответствующим обработчиком команд. Обработчик создает новый объект Order в модели домена, который генерирует NewcustomerCreatedEvent. Объект сохраняется в хранилище событий, и событие публикуется всем слушателям.

До сих пор так хорошо, но теперь вопрос. Где мы должны отправить SMS-уведомление?

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

Итак, мы создали домен SMS, и теперь, когда слушатель событий получает NewCustomerCreatedEvent, обработчик событий создает новую команду SendSmsMessageCommand, которая создаст новый объект SMSMessage в нашем домене, отправит уведомление SMS и создаст событие SmsSent, которое мы используем для создания представления.

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

Мы думали о отправке SMS на мероприятие SmSSent, но это было бы немного странно, потому что событие говорит, что сообщение уже отправлено, но это не так.

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

Надеюсь, вы, ребята, уже справились с этой ситуацией и можете дать нам несколько советов по этому вопросу.

Ответы

Ответ 1

Я бы подумал, что объект домена для SMS-сообщения не нужен. Вам просто нужно сообщить SMS, отправленные клиенту, правильно? SMS-сообщения не используются в какой-либо логике домена, правильно?

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

Ответ 2

Вы можете использовать сагу или диспетчер процессов, как ее называет Microsoft. Это в основном слушает события, которые изменяют состояние саги, и выдает команды на основе логики состояния, реализованной в саге.

В вашем случае это будет две государственные саги, которые ждут как CustomerCreatedEvent, так и OrderCreatedEvent, и либо выдают команду на отправку sms, если у вас есть специализированный ограниченный контекст для связи, либо вызов службы инфраструктуры через интерфейс, чтобы отправить смс.

Здесь вы можете найти статью Microsoft о шаблоне саги/процесса:

https://msdn.microsoft.com/en-us/library/jj591569.aspx

И две статьи, содержащие реализации:

http://danielwhittaker.me/2015/03/31/how-to-send-emails-the-right-way-in-a-cqrs-system/

http://blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-ii-of-ii/