Le parole sono importanti
Nel mondo dello sviluppo software, i termini di moda spesso generano confusione, soprattutto quando suonano simili ma si riferiscono a concetti completamente diversi. Due di questi termini “confondibili” sono Event Sourcing ed Event Streaming. Sebbene entrambi siano fondamentali nelle architetture moderne, essi servono scopi distinti e operano in ambiti completamente differenti.
Event Sourcing
Event Sourcing è un modello architetturale in cui ogni cambiamento di stato di un sistema, più precisamente di un aggregato nell’ambito di Domain-Driven Design, è rappresentato come un evento immutabile e memorizzato in ordine cronologico. Questo approccio consente di ricostruire lo stato del sistema in qualsiasi momento semplicemente rileggendo gli eventi.
Un database che supporta una simile architettura deve essere in grado di garantire la concorrenza ottimistica, il che significa che la lettura degli eventi in esso memorizzati è sempre consentita, senza nessuna restrizione: i vincoli riguardano esclusivamente gli aggiornamenti dei record. Già, ma trattandosi di una sequenza di fatti accaduti nel nostro sistema, ed essendo questi fatti immutabili, di fatto i vincoli intervengono solo nel momento in cui si cerca di aggiungere (append) un nuovo evento, perché di fatto non è consentito modificare un evento!
Un Event Store assegna un numero di versione, un progressivo, ad ogni evento che viene memorizzato. Quando due operazioni cercano di appendere contemporaneamente un evento sullo stesso stream dello stesso aggregato, Event Store verifica la versione. Se la versione dell’aggregato che sto cercando di salvare coincide con l’ultima versione dell’evento nello stream, allora salva e incrementa la versione. Negli altri casi respinge al mittente la richiesta.
Questa proprietà è fondamentale per garantire la Strong Consistency del nostro aggregato. Un sistema a eventi possiede sì la £ventual Consistency (“coerenza finale”) per quanto riguarda le repliche — i Read Model per attenerci alla nomenclatura di CQRS+ES — ha anche la Strong Consistency (“coerenza forte”) nel modello di scrittura.
Riepilogando, un Event Store deve poter
- garantire una cronologia completa delle modifiche di stato e abilitare funzionalità come audit e retrospettiva;
- memorizzare una sequenza immutabile di eventi.
Un database Event Store è il compagno di viaggio ideale per un’architettura CQRS in cui si vogliono salvare gli eventi si sistema, i Domain Event, per poter ricostruire lo stato dei nostri aggregati partendo proprio da questi eventi.
Un esempio classico, e concreto, potrebbe essere quello di un e-commerce, in cui Event Sourcing potrebbe rappresentare eventi come “OrdineDaCarrelloCreato”, “PagamentoTramiteCartaDiCreditoConfermato”, “OrdineConPrioritàAltaSpedito”, consentendo non solo di ricostruire l’intera cronologia di un ordine, ma anche di capire il processo di business sottostante. Proprio per questo motivo un Event Store è utilizzato come source-of-truth del sistema.
Event Streaming
L’altro protagonista di questo confronto è l’Event Streaming. Event Streaming è focalizzato sullo spostamento in tempo reale di informazioni, in pratica dati, da un oggetto a un altro all’interno di un sistema distribuito. Strumenti tipici che implementano questo paradigma sono Kafka, RabbitMQ, Azure Service Bus, AWS SQS e SNS.
Ogni elemento di un sistema distribuito può sottoscriversi a questi flussi e ricevere appunto in tempo reale informazioni che servono ad attivare azioni varie, come aggiornamento di database, attivazione o disattivazione di un’apparecchiatura in una piattaforma IoT, acquisto o vendita di un titolo in un sistema bancario, etc.
I sistemi per l’Event Streaming non sono progettati per essere dei database! Ovviamente hanno delle funzionalità di storage, ma non per essere utilizzate per lo scopo di un tradizionale database. Queste funzionalità sono state aggiunte per garantire resilienza e tolleranza ai guasti e, grazie ad esse, scenari come quelli in cui il broker interrompe il proprio servizio e il sistema ricevente non è disponibile sono perfettamente gestiti.
Qualcuno potrebbe obiettare che anche questi sistemi gestiscono un log append-only, proprio come un Event Store, ma gli eventi in esso contenuti non possono essere utilizzati come source-of-truth, almeno non come nel caso di un Event Store!
Confronto
Gli eventi sono la nostra source-of-truth solo se li si utilizza nel modello di scrittura come base per la reidratazione dello stato di un aggregato. Se si utilizzano gli eventi per costruire una vista, allora si esternalizza la verità su un altro storage, che paradossalmente, potrebbe ricevere aggiornamenti anche da altri stream, violando completamente la “catena di consistenza” dei fatti accaduti nel nostro aggregato. L’uso di strumenti per l’Event Streaming come strumenti di Event Sourcing porta proprio a questa conclusione.
Un altro aspetto critico è quello di mantenere gli stream brevi. In Kafka, ad esempio, non importa quanti eventi ci sono in un Topic, ossia, non importa quanto sia lungo il Topic stesso, perché si tratta di un canale di comunicazione. Il discorso è completamente diverso nel caso di un Event Store, perché in questo caso stiamo parlando di database, non di canali di comunicazione; quindi, più eventi ci sono nello stream, maggiore è la sua dimensione, peggiori sono le prestazioni. Questo aspetto non impatta solo sulle prestazioni, ma rende difficile il versionamento degli eventi, aspetto cruciale quando si parla di CQRS+ES, ma che esula dagli aspetti di questo articolo.
La seguente tabella prova a riepilogare le maggiori differenze fra Event Sourcing e Event Streaming.
Conclusioni
È facile cadere nella trappola dell’utilizzo di un sistema di Event Streaming come tuttofare. Le promesse sono molto allettanti, ma alla lunga si ottengono solo problemi. Costruire soluzioni software, lo ripeto, non è un esercizio di stile: è risolvere problemi di business.
I problemi di business mutano continuamente, costringendo i nostri sistemi ad adeguarsi; quindi, utilizziamo gli strumenti per il ruolo per cui sono stati progettati e, dalla lista delle cose da risolvere, toglieremo della complessità accidentale inutile.
Sono fondamentalmente un eterno curioso. Mi definisco da sempre uno sviluppatore backend, ma non disdegno curiosare anche dall'altro lato del codice. Mi piace pensare che "scrivere" software sia principalmente risolvere problemi di business e fornire valore al cliente, e in questo trovo che i pattern del DDD siano un grande aiuto. Lavoro come Software Engineer presso intré, un'azienda che sposa questa ideologia; da buon introverso trovo difficoltoso uscire allo scoperto, ma mi piace uscire dalla mia comfort-zone per condividere con gli altri le cose che ho imparato, per poter trovare ogni volta i giusti stimoli a continuare a migliorare.
Mi piace frequentare il mondo delle community, contribuendo, quando posso, con proposte attive. Sono co-founder della community DDD Open e Polenta e Deploy, e membro attivo di altre community come Blazor Developer Italiani.