Как бороться с побочными эффектами в Event Sourcing?

14

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

Я хотел бы получить предупреждение, которое является прямым следствием событий, происходящих в системе, без промежуточного представления, которое моделирует текущее состояние шаблона.

  1. Мониторинг активирован
  2. Транзакция обработана
  3. Транзакция обработана
  4. Транзакция обработана
  5. Оповещение сработало (id: 123)
  6. Email для оповещения отправлено (для id: 123)
  7. Транзакция обработана

Имея это в виду, я подумал, что источник событий может быть применим здесь очень хорошо, хотя у меня есть вопрос без четкого ответа. Оповещение, запущенное в этом примере, имеет явный побочный эффект: необходимо отправить электронное письмо, а это может произойти только один раз. Следовательно, этого не должно происходить при воспроизведении всех событий агрегата.

В какой-то степени я вижу электронное письмо, которое необходимо отправить, аналогично материализации, сгенерированной стороной запроса, которую я видел так много раз в литературе по поиску CQRS / Event, хотя с не столь тонким отличием.

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

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

Даже имея в виду это решение, я не знаю, является ли это намеком на то, что с этой архитектурой что-то не так для этой проблемы.

  • Правильный ли мой подход?
  • Есть ли место, где я могу найти больше информации об этом?

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

Огромное спасибо!

Иаков
источник

Ответы:

12

Как бороться с побочными эффектами в Event Sourcing?

Краткая версия: модель домена не имеет побочных эффектов. Это отслеживает их. Побочные эффекты выполняются с использованием порта, который подключается к границе; Когда электронное письмо отправлено, вы отправляете подтверждение обратно в модель домена.

Это означает, что электронное письмо отправляется вне транзакции, которая обновляет поток событий.

Именно там, где снаружи, дело вкуса.

Итак, концептуально, у вас есть поток событий, таких как

EmailPrepared(id:123)
EmailPrepared(id:456)
EmailPrepared(id:789)
EmailDelivered(id:456)
EmailDelivered(id:789)

И из этого потока вы можете создать складку

{
    deliveredMail : [ 456, 789 ],
    undeliveredMail : [123]
}

Сгиб сообщает вам, какие письма не были подтверждены, поэтому вы отправляете их снова:

undeliveredMail.each ( mail -> {
    send(mail);
    dispatch( new EmailDelivered.from(mail) );
}     

По сути, это двухэтапная фиксация: вы модифицируете SMTP в реальном мире, а затем обновляете модель.

Приведенный выше шаблон дает вам модель доставки как минимум один раз. Если вы хотите самое большее один раз, вы можете перевернуть его

undeliveredMail.each ( mail -> {
    commit( new EmailDelivered.from(mail) );
    send(mail);
}     

Существует барьер транзакции между тем, чтобы EmailPrepared был надежным, и фактически отправлял письмо. Существует также транзакционный барьер между отправкой электронной почты и обеспечением надежности EmailDelivered.

Надежная отправка сообщений от Udi Dahan с распределенными транзакциями может стать хорошей отправной точкой.

VoiceOfUnreason
источник
2

Вы должны отделить «События изменения состояния» от «Действия»

Изменение состояния событий является событие , которое изменяет состояние объекта. Это те, которые вы храните и воспроизводите.

Действие этого то объект и к другим вещам. Они не хранятся как часть Event Sourcing.

Один из способов сделать это с помощью обработчиков событий, которые вы подключаете или нет, в зависимости от того, хотите ли вы выполнить действия.

public class Monitor
{
    public EventHander SoundAlarm;
    public void MonitorEvent(Event e)
    {
        this.eventcount ++;
        if(this.eventcount > 10)
        {
             this.state = "ALARM!";
             if(SoundAlarm != null) { SoundAlarm();}
        }
    }
}

Теперь в моей службе мониторинга я могу иметь

public void MonitorServer()
{
    var m = new Monitor(events); //11 events
    //alarm has not been sounded because the event handler wasn't wired up
    //but the internal state is correctly set to "ALARM!"
    m.SoundAlarm += this.SendAlarmEmail;
    m.MonitorEvent(e); //email is sent
}

Если вам нужно регистрировать отправленные письма, вы можете сделать это как часть SendAlarmEmail. Но они не являются событиями в смысле Event Sourcing

Ewan
источник