Reidrating Aggregates da una proiezione "istantanee" piuttosto che dall'Event Store

11

Quindi ho flirtato con Event Sourcing e CQRS per un po 'di tempo, anche se non ho mai avuto l'opportunità di applicare i pattern su un vero progetto.

Comprendo i vantaggi di separare i tuoi dubbi di lettura e scrittura e apprezzo il modo in cui Event Sourcing semplifica la proiezione delle modifiche di stato nei database "Read Model" diversi dal tuo Event Store.

Ciò su cui non sono molto chiaro è il motivo per cui dovresti reidratare i tuoi Aggregati direttamente dall'Event Store.

Se la proiezione delle modifiche ai database "letti" è così semplice, perché non sempre le modifiche ai progetti in un database di "scrittura" il cui schema si adatta perfettamente al modello di dominio? Questo sarebbe effettivamente un database di istantanee.

Immagino che questo debba essere abbastanza comune nelle applicazioni ES + CQRS in natura.

Se questo è il caso, l'Event Store è utile solo quando si ricostruisce il database "write" come risultato delle modifiche dello schema? O mi manca qualcosa di più grande?

    
posta MetaFight 24.12.2017 - 21:41
fonte

3 risposte

12

What I'm not super clear on is why you would ever rehydrate your Aggregates from the Event Store itself.

Perché gli "eventi" sono il libro dei record.

If projecting changes to "read" databases is so easy, why not always project changes to a "write" database whose schema perfectly matches your domain model? This would effectively be a snapshot database.

Sì; a volte è utile l'ottimizzazione delle prestazioni per utilizzare una copia memorizzata nella cache dello stato aggregato, anziché rigenerare lo stato da zero ogni volta. Ricorda: la prima regola dell'ottimizzazione delle prestazioni è "Do not". Aggiunge ulteriore complessità alla soluzione e preferiresti evitarlo fino a quando non avrai una motivazione aziendale convincente.

If this is the case, is the Event Store only useful when rebuilding your "write" database as a result of schema changes? Or am I missing something bigger?

Ti manca qualcosa di più grande.

Il primo punto è che se stai considerando una soluzione basata su eventi, è perché ti aspetti che ci sia valore nel preservare la storia di ciò che è accaduto, cioè che vuoi apportare cambiamenti non distruttivi .

Ecco perché stiamo scrivendo al negozio di eventi.

In particolare, questo significa che ogni modifica deve essere scritta nell'archivio degli eventi.

Gli scrittori in competizione potrebbero potenzialmente distruggere le rispettive scritture o portare il sistema a uno stato non intenzionale, se non sono a conoscenza delle modifiche reciproche. Quindi il solito approccio quando hai bisogno di coerenza è quello di indirizzare le tue scritture a una posizione specifica nel journal (analogo a un PUT condizionale in un API HTTP). Una scrittura fallita dice allo scrittore che la loro attuale comprensione del diario non è sincronizzata e che dovrebbero essere ripristinati.

Il ritorno a una posizione nota e quindi la riproduzione di eventuali eventi aggiuntivi da quel momento è una strategia di recupero comune. Quella nota buona posizione può essere una copia di ciò che è nella cache locale o una rappresentazione nel tuo archivio di istantanee.

Nel percorso felice, puoi conservare un'istantanea dell'aggregato in memoria; devi solo raggiungere un negozio esterno quando non è disponibile una copia locale.

Inoltre, non è necessario essere completamente catturati, se si ha accesso al libro dei record.

Quindi il solito approccio ( if che utilizza un repository di istantanee) è di mantenerlo in modo asincrono . In questo modo, se hai bisogno di recuperare, puoi farlo senza ricaricare e riprodurre l'intera cronologia dell'aggregato.

Ci sono molti casi in cui questa complessità non è interessante, perché gli aggregati a grana fine con vite passate di solito non raccolgono abbastanza eventi perché i benefici superino i costi di mantenimento di una cache di istantanee.

Ma quando è lo strumento giusto per il problema, caricare una rappresentazione obsoleta dell'aggregato nel modello di scrittura, quindi aggiornarlo con eventi aggiuntivi, è una cosa perfettamente ragionevole da fare.

    
risposta data 25.12.2017 - 05:46
fonte
6

Dato che non si specifica quale sarebbe lo scopo del database di "scrittura", assumerò qui che ciò che intendi è questo: quando registri un nuovo aggiornamento a un aggregato, invece di ricostruire l'aggregato dall'evento archivia, lo sollevi dal database "scrivi", convalidi la modifica ed emetti un evento.

Se questo è ciò che intendi, allora questa strategia creerà una condizione di incoerenza: se un nuovo aggiornamento avviene prima che l'ultimo abbia avuto la possibilità di inserirlo nel database "write", il nuovo aggiornamento finirà per essere convalidato dati obsoleti, quindi potenzialmente emettendo un evento "impossibile" (cioè "non consentito" e corrompendo lo stato del sistema.

Ad esempio, considera un esempio permanente di prenotazione di posti in un teatro. Per evitare la doppia prenotazione, è necessario assicurarsi che il posto prenotato non sia già stato preso - questo è ciò che chiami "convalida". Per fare ciò, memorizzi una lista di posti già prenotati nel database "scrivi". Quindi, quando arriva una richiesta di prenotazione, si controlla se il posto richiesto è nella lista, e in caso contrario, si emette un evento "prenotato", altrimenti si risponde con un messaggio di errore. Quindi esegui un processo di proiezione, in cui ascolti gli eventi "prenotati" e aggiungi i posti prenotati all'elenco nel database "scrivi".

Normalmente, il sistema funzionerebbe in questo modo:

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Projection process catches the event, adds seat #1 to the "already booked" list.
5. Another request to book seat #1.
6. Check in the list: the list contains seat #1
7. Respond with an error message.

Tuttavia, se le richieste arrivano troppo rapidamente e il passaggio 5 si verifica prima del punto 4?

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Another request to book seat #1.
5. Check in the list: the list is still empty.
6. Issue another "booked seat #1" event.

Ora hai due eventi per prenotare lo stesso posto. Lo stato del sistema è danneggiato.

Per evitare che ciò accada, non devi mai convalidare gli aggiornamenti rispetto a una proiezione. Per convalidare un aggiornamento, si ricostruisce l'aggregato dall'archivio degli eventi, quindi si convalida l'aggiornamento con esso. Successivamente, si emette un evento, ma si utilizza la protezione timestamp per assicurarsi che non siano stati rilasciati nuovi eventi dall'ultima volta in cui si è letto dal negozio. Se fallisce, riprova.

La ricostruzione di aggregati dall'archivio eventi potrebbe comportare una penalizzazione delle prestazioni. Per mitigarlo, è possibile archiviare istantanee aggregate direttamente nel flusso di eventi, taggato con l'ID dell'evento dal quale è stata creata l'istantanea. In questo modo, puoi ricostruire l'aggregato caricando lo snapshot più recente e ripetendo solo gli eventi successivi, invece di riprodurre sempre l'intero flusso di eventi dall'inizio del tempo.

    
risposta data 24.12.2017 - 23:19
fonte
3

Il motivo principale è la prestazione. È possibile memorizzare uno snapshot per ogni commit (commettere = gli eventi generati da un unico comando, di solito un solo evento), ma questo è costoso. Oltre all'istantanea è necessario memorizzare anche il commit, altrimenti non sarebbe l'event sourcing. E tutto ciò deve essere fatto atomicamente, tutto o niente. La tua domanda è valida solo se vengono utilizzati database / tabelle / raccolte separate (altrimenti sarebbe esattamente il caso di approvvigionamento degli eventi), quindi sei obbligato a utilizzare le transazioni per garantire la coerenza. Le transazioni non sono scalabili. Un flusso di eventi append-only (l'archivio eventi) è la madre della scalabilità.

Il secondo motivo è l'incapsulamento aggregato. Devi proteggerlo. Ciò significa che l'aggregato dovrebbe essere libero di modificare la propria rappresentazione interna in qualsiasi momento. Se lo si archivia e si dipende strongmente da esso, si avrà un periodo molto difficile con il controllo delle versioni, cosa che succederà di sicuro. Nella situazione in cui si utilizza l'istantanea solo come ottimizzazione, quando lo schema cambia semplicemente ignora tali istantanee ( semplicemente ?) Non lo penso davvero, buona fortuna Modifiche allo schema di aggregazione, comprese tutte le entità nidificate e gli oggetti valore, in modo efficiente e gestendo ciò).

    
risposta data 25.12.2017 - 02:38
fonte

Leggi altre domande sui tag