Come gestire gli effetti collaterali in CRQS durante la riproduzione di eventi?


10

Si dice che in CQRS è facile correggere un bug, basta ridistribuire e quindi riprodurre gli eventi.

Ma cosa succederebbe se uno degli eventi dovesse causare un sistema esterno non sotto il tuo controllo per "spedire un articolo" al cliente se riproducessi semplicemente gli eventi l'articolo verrebbe spedito due volte.

Come lo risolvi?

Risposte:


6

Devi fare una chiara separazione tra gli eventi che modificano lo stato del tuo modello di lettura e gli eventi (potenzialmente) che modificano lo stato dei sistemi esterni. Assicurati di non avere "eventi misti" che modificano entrambi gli stati insieme. In questo modo, è possibile riprodurre i propri eventi in una "modalità di riproduzione" specifica in cui tali eventi per il sistema esterno non vengono nuovamente attivati. In questa modalità, "simuli" anche gli eventi che erano stati originariamente avviati dal sistema esterno (ora li prendi invece dalla coda di riproduzione).

Non dimenticare, il passaggio di ridistribuzione significa in realtà ripristinare lo stato del modello letto a un momento precedente. Questo probabilmente non è nulla che tu possa fare (o dover fare) per lo stato dei sistemi esterni.


"Non dimenticare, il passaggio di ridistribuzione significa in realtà ripristinare lo stato del modello letto a un momento precedente. Probabilmente questo non è nulla che puoi fare (o dover fare) per lo stato dei sistemi esterni." -> ma cosa succede se desidero che il mio replay riprovi chiamate di sistema esterne non riuscite come la spedizione? in questo caso la mia ridistribuzione di un replay non solo ripristinerebbe lo stato del modello di lettura ma causerebbe anche eventi esterni, sembra giusto o mi manca qualcosa?
Jas,

2
@Jas: non si desidera abusare del "replay" per ritentare una chiamata di sistema esterna non riuscita. Si utilizza il "replay" per ottenere il modello di lettura del proprio sistema nello stesso stato in cui era prima. Ciò significa che in caso di una richiesta di spedizione non riuscita, il sistema è stato informato in precedenza di questo errore e memorizzato tali informazioni in qualche punto del suo stato. Il replay si assicura che queste informazioni siano ancora presenti dopo "ridistribuzione e riproduzione". Quindi, dopo il replay, il tuo sistema potrebbe applicare una strategia di "riprovare spedizione in caso di guasto" (che non ha nulla a che fare con CQRS, qualsiasi sistema di ordinazione robusto dovrebbe avere una tale strategia).
Doc Brown,

Interessante, questo è quello che avevo in mente di fare, mi chiedevo solo se ci fosse uno "schema" su questo, quindi non reinventare la ruota!
Jas,

3

Dall'articolo sull'approvvigionamento di eventi di Martin Fowler :

L'idea fondamentale di Event Sourcing è quella di garantire che ogni cambiamento allo stato di un'applicazione venga acquisito in un oggetto evento e che questi oggetti evento vengano essi stessi memorizzati nella sequenza in cui sono stati applicati per la stessa durata dello stato dell'applicazione stesso.

Quindi, quando è necessario ripristinare lo stato del sistema in un determinato momento, si riproduce lo stato memorizzato , non i gestori di eventi, fino a quel momento.

Detto questo, se stai lavorando solo con dati di stato, non dovrebbero esserci effetti sul sistema esterno. A meno che tu non abbia trigger o watcher nel tuo negozio di eventi, nel qual caso dovresti disabilitarli per la durata del ripristino. Dato che dici di non avere alcun controllo sul sistema esterno, non dovresti tentare di ripristinarne lo stato usando l'API esposta poiché non sai quali effetti collaterali potrebbe avere nel loro sistema. Se il ripristino pone il sistema in uno stato intermedio (ad es. A causa di operazioni fallite nel sistema esterno), ciò non dovrebbe rientrare nelle responsabilità di un replay di eventi.


2

Ma cosa succederebbe se uno degli eventi dovesse causare un sistema esterno non sotto il tuo controllo per "spedire un articolo" al cliente se riproducessi semplicemente gli eventi l'articolo verrebbe spedito due volte.

Per scegliere un esempio specifico, consideriamo come potrebbe funzionare un approccio "almeno una volta" agli effetti collaterali.

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

Quindi il modello di dominio tiene traccia di ciò che deve essere fatto; ma lascia il vero fare all'applicazione

Nel contesto dell'esecuzione di un comando, l'idea di base sembra la stessa. Gli effetti collaterali effettivi si verificano al di fuori della transazione che aggiorna il modello.

Quindi i test unitari per il tuo modello potrebbero essere simili

{
    // 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()
}

I punti principali qui sono

  • L'aggiornamento del modello è privo di effetti collaterali; gli effetti collaterali effettivi si verificano al di fuori della transazione che aggiorna il modello.
  • Un evento che descrive l'esito dell'effetto collaterale deve tornare.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.