Jak radzić sobie z efektami ubocznymi w Event Sourcing?

14

Załóżmy, że chcemy wdrożyć mały podsystem bezpieczeństwa dla aplikacji finansowej, która ostrzega użytkowników za pośrednictwem poczty e-mail w przypadku wykrycia dziwnego wzorca. W tym przykładzie wzorzec będzie składał się z trzech transakcji, jak pokazano. Podsystem zabezpieczeń może odczytywać zdarzenia z systemu głównego z kolejki.

Chciałbym otrzymać alert, który jest bezpośrednią konsekwencją zdarzeń, które mają miejsce w systemie, bez pośredniej reprezentacji, która modeluje bieżący stan wzorca.

  1. Monitorowanie aktywowane
  2. Transakcja przetworzona
  3. Transakcja przetworzona
  4. Transakcja przetworzona
  5. Alarm wyzwolony (id: 123)
  6. Wysłano wiadomość e-mail z ostrzeżeniem (dla identyfikatora: 123)
  7. Transakcja przetworzona

Mając to na uwadze, pomyślałem, że pozyskiwanie wydarzeń może mieć tutaj zastosowanie bardzo dobrze, chociaż mam pytanie bez jasnej odpowiedzi. Alarm wywołany w przykładzie ma wyraźny efekt uboczny, należy wysłać wiadomość e-mail, co powinno nastąpić tylko raz. Dlatego nie powinno się to zdarzyć podczas odtwarzania wszystkich zdarzeń agregatu.

W pewnym stopniu widzę wiadomość e-mail, która musi zostać wysłana, podobnie jak materializacje wygenerowane przez stronę zapytania, którą widziałem tyle razy w literaturze dotyczącej CQRS / zaopatrzenia w zdarzenia, jednak z tak niewielką różnicą.

W tej literaturze strona zapytania jest zbudowana z procedur obsługi zdarzeń, które mogą generować materializację stanu w danym punkcie, ponownie odczytując wszystkie zdarzenia. Jednak w tym przypadku nie można tego dokładnie zrobić z powodów wyjaśnionych wcześniej. Idea, że ​​każdy stan jest przejściowy, nie ma tu tak dobrego zastosowania . Musimy odnotować fakt, że gdzieś wysłano alert.

Łatwym rozwiązaniem byłoby dla mnie posiadanie innej tabeli lub struktury, w której prowadziłbyś zapisy wcześniej uruchomionych alertów. Ponieważ mamy identyfikator, moglibyśmy sprawdzić, czy wcześniej wysłano alert o tym samym identyfikatorze. Posiadanie tych informacji sprawiłoby, że SendAlertCommand byłby idempotentny. Można wydać kilka poleceń, ale efekt uboczny wystąpi tylko raz.

Nawet mając to na uwadze, nie wiem, czy jest to wskazówka, że ​​w tej architekturze jest coś nie tak z tym problemem.

  • Czy moje podejście jest prawidłowe?
  • Czy jest jakieś miejsce, w którym mogę znaleźć więcej informacji na ten temat?

Dziwne, że nie udało mi się znaleźć więcej informacji na ten temat. Może użyłem złego sformułowania.

Dziękuję bardzo!

Jakub
źródło

Odpowiedzi:

12

Jak radzić sobie z efektami ubocznymi w Event Sourcing?

Krótka wersja: model domeny nie wywołuje skutków ubocznych. To śledzi je. Efekty uboczne są wykonywane przy użyciu portu, który łączy się z granicą; po wysłaniu wiadomości e-mail potwierdzenie jest wysyłane z powrotem do modelu domeny.

Oznacza to, że wiadomość e-mail jest wysyłana poza transakcją, która aktualizuje strumień zdarzeń.

Właśnie tam, gdzie na zewnątrz jest kwestia gustu.

Tak koncepcyjnie, masz strumień wydarzeń takich jak

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

Z tego strumienia możesz utworzyć zakładkę

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

Fold informuje, które e-maile nie zostały potwierdzone, więc wysyłasz je ponownie:

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

W rzeczywistości jest to zatwierdzenie dwufazowe: modyfikujesz SMTP w świecie rzeczywistym, a następnie aktualizujesz model.

Powyższy wzór daje model dostawy przynajmniej raz. Jeśli chcesz co najwyżej raz, możesz to odwrócić

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

Istnieje bariera transakcyjna między uczynieniem EmailPrepared trwałym a faktycznym wysyłaniem wiadomości e-mail. Istnieje również bariera transakcyjna między wysyłaniem wiadomości e-mail a tworzeniem usługi EmailDelivered.

Niezawodny komunikat Udi Dahana z rozproszonymi transakcjami może być dobrym punktem wyjścia.

VoiceOfUnreason
źródło
2

Musisz oddzielić zdarzenia „Zmiana stanu” od „Działania”

State Zmień Event to wydarzenie, które zmienia stan obiektu. Te są przechowywane i odtwarzane.

Akcja jest czymś obiekt robi z innymi. Nie są one przechowywane jako część Sourcing zdarzeń.

Jednym ze sposobów na to jest w przypadku uchwytów zdarzeń, które łączysz, czy nie, w zależności od tego, czy chcesz uruchomić Akcje.

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

Teraz w mojej usłudze monitorowania mogę mieć

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
}

Jeśli musisz zalogować wysłane wiadomości e-mail, możesz to zrobić w ramach SendAlarmEmail. Ale nie są to wydarzenia w rozumieniu Event Sourcing

Ewan
źródło