Jak radzić sobie z efektami ubocznymi w CRQS podczas odtwarzania zdarzeń?

10

Mówi się, że w CQRS łatwo jest naprawić błąd, wystarczy ponownie wdrożyć, a następnie odtworzyć zdarzenia.

Ale co jeśli jedno ze zdarzeń spowoduje, że zewnętrzny system, który nie jest w twojej kontroli, „wyśle ​​przedmiot” do klienta, jeśli po prostu odtworzysz zdarzenia, przedmiot zostanie wysłany dwukrotnie.

Jak to rozwiązujesz?

Jas
źródło

Odpowiedzi:

6

Konieczne jest wyraźne oddzielenie zdarzeń modyfikujących stan modelu odczytu od zdarzeń (potencjalnie) modyfikujących stan systemów zewnętrznych. Upewnij się, że nie masz żadnych „mieszanych zdarzeń” modyfikujących oba stany razem. W ten sposób możesz odtwarzać zdarzenia w określonym „trybie odtwarzania”, w którym zdarzenia dla systemu zewnętrznego nie są ponownie uruchamiane. W tym trybie „symulujesz” również zdarzenia, które zostały pierwotnie zainicjowane przez system zewnętrzny (teraz pobierasz je z kolejki odtwarzania).

Nie zapomnij, że krok ponownego wdrożenia oznacza zresetowanie stanu odczytanego modelu do wcześniejszego momentu. Prawdopodobnie nie jest to nic, co możesz zrobić (lub zrobić) dla stanu systemów zewnętrznych.

Doktor Brown
źródło
„Nie zapomnij, że krok ponownego wdrożenia oznacza zresetowanie stanu odczytanego modelu do wcześniejszego momentu. Prawdopodobnie nie jest to nic, co możesz zrobić (lub zrobić) dla stanu systemów zewnętrznych.” -> ale co jeśli chcę, aby moja ponowna próba powtórzyła nieudane zewnętrzne wywołania systemowe, takie jak wysyłka? w takim przypadku moje ponowne wdrożenie powtórki nie tylko zresetuje stan odczytu modelu, ale także spowoduje zdarzenia zewnętrzne, czy to brzmi uczciwie, czy czegoś brakuje?
Jas
2
@Jas: nie chcesz nadużywać funkcji „powtórz”, aby ponowić nieudane zewnętrzne wywołanie systemowe. „Powtórka” służy do uzyskania modelu odczytu własnego systemu w takim samym stanie, w jakim był wcześniej. Oznacza to, że w przypadku nieudanego żądania wysyłki Twój system został wcześniej poinformowany o tej awarii i zapisał te informacje gdzieś w swoim stanie. Powtórka upewnia się, że ta informacja jest nadal dostępna po „wdrożeniu i odtworzeniu”. Dlatego po powtórce system może zastosować strategię „ponów wysyłkę w przypadku awarii” (która nie ma nic wspólnego z CQRS, każdy solidny system zamawiania powinien mieć taką strategię).
Doc Brown
Ciekawe, właśnie to miałem na myśli, zastanawiałem się tylko, czy jest na tym jakiś „wzór”, więc nie wymyślam na nowo koła!
Jas
3

Z artykułu Martina Fowlera poświęconego zaopatrzeniu w wydarzenia :

Podstawową ideą Event Sourcing jest zapewnienie, że każda zmiana stanu aplikacji jest rejestrowana w obiekcie zdarzenia oraz że te obiekty zdarzeń są same przechowywane w kolejności, w jakiej zostały zastosowane przez ten sam okres istnienia, co sam stan aplikacji.

Kiedy więc musisz przywrócić stan systemu do określonego momentu, odtwarzasz stan przechowywany , a nie procedury obsługi zdarzeń, aż do tego momentu.

Biorąc to pod uwagę, jeśli pracujesz tylko z danymi stanu, nie powinno to mieć żadnego wpływu na system zewnętrzny. O ile nie masz wyzwalaczy lub obserwatorów w swoim sklepie zdarzeń, w takim przypadku powinieneś je wyłączyć na czas przywracania. Ponieważ mówisz, że nie masz kontroli nad systemem zewnętrznym, nie powinieneś podejmować żadnych prób przywrócenia jego stanu za pomocą odsłoniętego interfejsu API, ponieważ nie wiesz, jakie skutki uboczne może mieć w tym systemie. Jeśli przywrócenie powoduje, że system znajduje się w stanie pośrednim (np. Z powodu nieudanych operacji w systemie zewnętrznym), nie powinno to wchodzić w zakres odpowiedzialności za odtworzenie zdarzenia.

diabelnie
źródło
2

Ale co jeśli jedno ze zdarzeń spowoduje, że zewnętrzny system, który nie jest w twojej kontroli, „wyśle ​​przedmiot” do klienta, jeśli po prostu odtworzysz zdarzenia, przedmiot zostanie wysłany dwukrotnie.

Aby wybrać konkretny przykład, zastanówmy się, jak może działać „przynajmniej raz” podejście do skutków ubocznych.

State currentState = State.InitialState
for(Event e : events) {
    currentState = currentState.apply(e)
}
for(SideEffect s : currentState.querySideEffects()) {
    performSideEffect(s)

Model domeny śledzi więc, co należy zrobić; ale pozostawia faktyczne działanie aplikacji

W kontekście uruchamiania polecenia podstawowy pomysł wygląda tak samo. Rzeczywiste skutki uboczne występują poza transakcją aktualizującą model.

Testy jednostkowe dla twojego modelu mogą wyglądać mniej więcej tak

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced)

    // Then
    List.of(SendEmail) === currentState.applyAll(events).querySideEffects()
}

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced, EmailSent)

    // Then
    List.EMPTY === currentState.applyAll(events).querySideEffects()
}

Głównymi punktami są tutaj

  • Aktualizacja modelu nie powoduje skutków ubocznych; rzeczywiste skutki uboczne występują poza transakcją aktualizującą model.
  • Wydarzenie opisujące wynik działania niepożądanego musi wrócić.
VoiceOfUnreason
źródło