CQRS-Event Sourcing: come elaborare gli eventi nell'ordine previsto all'interno del modello di lettura

4

Sto cercando alcune informazioni su come gestire il modello di lettura in un'applicazione CQRS Event Sourcing, al fine di fare il meglio per garantire la coerenza dei dati.

Il punto mancante è come essere sicuri (in realtà non è sicuro, ma ragionevolmente sicuro) che le proiezioni all'interno del modello di lettura siano in grado di elaborare gli eventi creati dal modello di scrittura nell'ordine previsto (che è l'ordine in cui il modello di scrittura ha generato gli eventi) .

Proviamo a chiarire lo scenario con un esempio.

Supponiamo di avere un'app con uno stack di scrittura che genera eventi per un aggregato chiamato news e leggere stack fatto da una singola proiezione, che è fondamentalmente una tabella denormalizzata che elenca tutte le notizie con alcune proprietà ( ad esempio il titolo, il riassunto e il nome dell'autore).
In questo modo, un'app client è in grado di visualizzare un'interfaccia utente che elenca tutte le ultime notizie pubblicate dagli editori.

L'infrastruttura per il sourcing di eventi è un archivio eventi che salva gli eventi come documenti all'interno di una raccolta MongoDB e quindi pubblica un messaggio corrispondente su un bus di servizio, in modo che con un classico modello pub-sub tutte le proiezioni interessate sono in grado di sottoscrivere il messaggio e fare il lavoro adeguato in risposta ad esso.
Per coloro che hanno familiarità con RabbitMQ , questo tipo di cose potrebbe essere implementato utilizzando uno fanout exchange . Con Bus di servizio di Azure , invece, puoi utilizzare un argomento .
In questo tipo di scenario, il modello di scrittura e il modello di lettura vengono distribuiti su macchine diverse che possono essere ridimensionate indipendentemente l'una dall'altra, in base al carico della richiesta sia sullo stack di scrittura che sullo stack di lettura.

Dato questo scenario, è del tutto possibile che in un dato momento ci siano due o più istanze dell'app che ospita il modello di lettura del nostro sistema . Queste istanze saranno consumatori concorrenti sugli eventi pubblicati dal modello di scrittura.

Immagina ora che in un breve intervallo di tempo due eventi, ad esempio E1 ed E2, siano pubblicati sul bus di servizio e che ci siano due istanze in esecuzione, ad esempio M1 e M2, dell'app che ospita il modello letto.
Dato questo scenario è del tutto possibile che l'evento E1 venga elaborato dalla macchina M1 e che l'evento E2 sia elaborato contemporaneamente dalla macchina M2 (ricorda che le due istanze sono concorrenti sui messaggi pubblicati a il bus di servizio). A questo punto lo stato finale della proiezione è imprevedibile , perché ognuna delle istanze potrebbe essere più veloce dell'altra.

Un esempio tipico è quando entrambi gli eventi sono di tipo TitleSet , perché un editore ha deciso di cambiare il titolo delle notizie due volte in un intervallo di tempo molto breve: al termine dell'elaborazione la proiezione contiene il titolo sbagliato per le notizie e il titolo errato sarà visibile agli utenti finali nell'applicazione client (che, ovviamente, ottiene i dati per l'interfaccia utente dalla proiezione).

Qual è il modo migliore per gestire questo tipo di scenario in modo da garantire la massima coerenza possibile nello stack di lettura dell'applicazione?

P.S .: gli eventi E1 ed E2 sono stati generati nell'ordine atteso dal modello di scrittura e sono stati memorizzati correttamente all'interno dell'Event Store. L'incoerenza dei dati di cui stiamo parlando riguarda solo il modello di lettura .

    
posta Enrico Massone 11.07.2018 - 14:55
fonte

1 risposta

4

The infrastructure for the event sourcing is an event store which saves the events as documents inside a MongoDB collection and then publish a corresponding message to a service bus, so that with a classic pub-sub pattern all the interested projections are able to subscribe the message and do the proper work in response to it.

(Enfasi aggiunta)

Questa supposizione qui è quella che vuoi rimandare. Pub / Sub può funzionare per i consumatori che si preoccupano solo di un singolo messaggio in isolamento. I consumatori che hanno bisogno di uno stato dovrebbero consumare storie, non eventi.

Nel caso irrimediabilmente non ottimizzato, un consumatore legge l'intera cronologia degli eventi ordinati ogni volta che viene eseguito, quindi li elabora tutti.

Una versione ottimizzata di questo è che l'utente tiene traccia di dove è stato interrotto nella cronologia eventi e avvia una query "tutti gli eventi dall'evento X" per scoprire cosa è successo. Il caso irrimediabilmente non ottimizzato è semplicemente il caso degenerato di questo: "tutti gli eventi poiché non c'erano eventi".

Potresti ancora vedere il modello pub / sub applicato, non per ricostruire il modello letto, ma per svegliare il consumatore per estrarre la cronologia come descritto sopra (in effetti diventa un meccanismo di riduzione della latenza).

Non c'è niente di sbagliato nel fatto che il consumatore abbia un po 'di intelligenza nel riconoscere che l'evento ricevuto è l'immediato successore di ciò che è già noto. Normalmente questo viene eseguito con i metadati associati all'evento, che indicano la sua posizione nella cronologia.

Quindi, nel tuo scenario di consumatori in competizione, potresti vedere due diversi comportamenti di lettura al lavoro. Il ricevitore di E1 determina che E1 è l'immediato successore dello stato precedente e va semplicemente al lavoro. Il ricevitore di E2 vede dai metadati che manca almeno un evento, quindi aggiorna la sua copia del flusso di eventi, ricevendo in cambio la sequenza [E1, E2], che poi consuma.

Alcuni riferimenti

At the DDD Europe conference, I realized that the speakers I talked with where avoiding Pub/Sub whenever possible. -- Raymond Rutjes, 2016

Greg Young, Dati Polyglot (2014) ; Greg parla un po 'dei vantaggi di pull.

    
risposta data 11.07.2018 - 15:53
fonte

Leggi altre domande sui tag