Introduzione al Sourcing degli eventi e al CQRS

Segregazione di responsabilità per gli eventi e per i comandi (CQRS) sono diventati modelli fondamentali per la costruzione di sistemi moderni e distribuiti. In combinazione con architetture serverless, questi modelli sbloccano scalabilità senza precedenti, resilienza e verificabilità. Questo articolo fornisce un'esplorazione approfondita dell'attuazione di sourcing eventi e CQRS in ambienti serverless, che coprono concetti di base, strategie di implementazione pratica, trappole comuni e best practice del mondo reale.

Sourcing evento: Storing Change come una sequenza di eventi

Event Sourcing è un modello di persistenza dei dati in cui ogni cambiamento allo stato dell'applicazione viene catturato come un evento immutabile. Invece di memorizzare solo lo stato attuale, il sistema registra un registro cronologico degli eventi. Lo stato attuale può essere ricostruito rielaborando tali eventi. Questo approccio fornisce un percorso di audit completo, consente query temporali (ad esempio, "qual è stato in una data data data specifica?"), e semplifica il debugging e la conformità.

In un contesto serverless, il negozio di eventi deve essere altamente durevole, scalabile e a bassa latenza. Le scelte comuni includono AWS DynamoDB[], ]Azure Cosmos DB]], o Google Cloud Firestore].

L’articolo canonico di Martin Fowler [] sull’attenuazione degli eventi[] rimane un riferimento definitivo per comprendere le sfumature del modello.

Struttura e schema degli eventi

Ogni evento dovrebbe contenere al minimo: un tipo di evento, un timestamp, un identificatore aggregato, un numero di versione e un carico utile con i dati che sono cambiati. Utilizzando un registro di schema (ad esempio Google Cloud Registry[]] o ]AWS EventBridge Registry]]]) aiuta a mantenere la compatibilità all'indievolversità degli eventi.

CQRS: Separare le letture dagli scritti

CQRS (Command Query Responsibility Segregation) decouplizza i modelli utilizzati per gestire i comandi (scritture) da quelli utilizzati per gestire le query (leggi). In un'architettura serverless, questo significa distribuire funzioni o servizi separati: i gestioni di comando elaborano scriva, spesso appending eventi al negozio di eventi, mentre i gestori di query leggono da modelli di lettura ottimizzati, tabelle di ricerca, visualizzazioni materializzate, indicizzate.

Questa separazione porta vantaggi significativi: i carichi di lavoro di scrittura rimangono magra e focalizzati sulla validazione e sulla persistenza degli eventi, mentre i modelli di lettura possono essere sintonizzati per il recupero rapido, tra cui pre-joins, aggregazioni e funzionalità di ricerca full-text.

La documentazione originale di Greg Young CQRS[] fornisce un contesto fondamentale per il modello.

Combinazione di Sourcing degli eventi e CQRS in Serverless

Quando vengono utilizzati insieme, Event Sourcing e CQRS formano un potente duo: i comandi producono eventi memorizzati nel registro eventi e le proiezioni (o abbonati) aggiornano asincronicamente i modelli di lettura. Le piattaforme senza server eccelleno a questo paradigma organizzato dagli eventi perché astraggono la gestione delle infrastrutture e scalano automaticamente ogni componente basato sul carico.

Di seguito è riportato un tipico flusso di sistema senza server:

  1. L'azione utente[[]] attiva una funzione di comando (ad esempio, un AWS Lambda dietro API Gateway).
  2. La funzione di comando convalida l'ingresso, produce uno o più eventi di dominio, e li aggiunge al negozio di eventi (DynamoDB, Cosmos DB, ecc.).
  3. Dopo aver appiccato gli eventi, la funzione pubblica un messaggio (ad esempio, ad Amazon EventBridge, Azure Event Grid, o Google Pub/Sub) che indica che sono disponibili nuovi eventi.
  4. Le funzioni di proiezione[[]] si abbonano al flusso dell'evento e aggiornano il modello di lettura (ad esempio, una tabella DynamoDB denormalizzata, un indice di Elasticsearch, o una cache come Redis).
  5. Le funzioni di query servono richieste di lettura direttamente dal modello di lettura, mai query il negozio di eventi.

Questo disegno assicura coerenza eventuale[[]] tra i lati di scrittura e lettura, che è un core trade-off di CQRS. In molti domini aziendali, la consistenza eventuale è accettabile e anche desiderabile perché permette una maggiore produttività e una minore latenza per le letture.

Esempio: Gestione ordini di E-Commerce

Un utente mette un ordine (comand), che emette un evento . Una funzione di proiezione legge che l'evento e aggiorna un modello di lettura sommaria dell'ordine che include il nome del prodotto, la quantità e lo stato attuale. Un'altra proiezione potrebbe aggiornare un modello di lettura dell'inventario. Se l'utente richiede più tardi la cronologia degli ordini, la funzione di query legge dal modello di sommario pre-costruito, evitando costosi unisci o le fatture dall'evento.

Implementare lo Store degli eventi in database senza server

Con DynamoDB, un approccio comune è quello di utilizzare una singola tabella con una chiave primaria composita: (chiave di partizione) e (chiave di selezione) Questo consente un rapido recupero di tutti gli eventi per un aggregato specifico in ordine.

Per i carichi di lavoro che richiedono domande incrociate, si consideri l'utilizzo di un indice secondario su tipo di evento o timestamp. Tuttavia, evitare di scansionare l'intero negozio di eventi; tali esigenze sono meglio servite da modelli di lettura dedicati.

Su Azure, Cosmos DB offre capacità simili con livelli di coerenza configurabili e indicizzazione automatica. Il Azure Architecture Center's Event Sourcing pattern[[] fornisce una guida specifica a quella piattaforma.

Concorrenza e Idempotency

Concurrent writes to the same aggrega deve essere gestito con attenzione. Utilizzando controllo di concurrency ottimistico[ (ad esempio, aggiornamento condizionale con controllo della versione in DynamoDB) assicura che solo un comando riesce per incremento di versione. In caso di conflitto, il comando può essere riattivato dopo aver riletto gli ultimi eventi.

Modelli di lettura edificio con proiezioni

Le proiezioni sono funzioni che consumano eventi e aggiornano uno o più modelli di lettura. In serverless, sono meglio implementate come funzioni event-driven[] attivate dall'autobus dell'evento. Ogni funzione di proiezione dovrebbe essere idemponte: se un evento viene elaborato più di una volta (ad esempio, a causa di una riprovazione), l'aggiornamento del modello di lettura deve produrre lo stesso risultato.

Le strategie comuni per costruire modelli di lettura includono:

  • Tavoli normalizzati[[] in DynamoDB o Cosmos DB che rispecchiano i modelli di query (ad esempio, tutti gli ordini per un utente).
  • Indici di ricerca[] in Elasticsearch, Amazon OpenSearch, o Azure Cercare query full-text e sfaccettate.
  • I punti di vista collegati[[]]] utilizzando framework di streaming come AWS Kinesis Data Analytics o Azure Stream Analytics.
  • Cacche di memoria[] (ad esempio, ElastiCache, Redis) per query di latenza ultra-bassa, con invalidazione basata su TTL.

Per evitare un accoppiamento stretto, le proiezioni devono essere senza condizioni e unicamente guidate dal carico di pagamento dell'evento, possono essere aggiunte, rimosse o modificate senza influire sul lato di comando.

Gestione di eventuali condizioni e SAGA

Una delle sfide più grandi di un sistema CQRS/ES è la gestione della coerenza e il coordinamento delle transazioni commerciali multi-step. Un utente può effettuare un ordine, ma il modello di lettura potrebbe non riflettere che il cambiamento per qualche centinaio di millisecondi. Per le aspettative degli utenti sincroni (ad esempio, mostrando una pagina di conferma), il gestore di comando può restituire immediatamente l'ID dell'evento mentre il frontend polls per l'aggiornamento del modello di lettura o sottoscrivere canale di abbonamenti a un WebSock.

Per i processi multi-step che richiedono transazioni distribuite, il modello SAGA] è la soluzione preferita. Ogni passo nella saga emette eventi, e gli eventi compensativi vengono memorizzati nel negozio eventi per annullare i passaggi parzialmente completati. Funzioni senza server e orchestre durevoli (ad esempio, funzioni passo AWS, funzioni durevoli Azure, flussi di lavoro di Google) possono implementare in modo affidabile lungo.

Gestione degli errori e Idempotency a Scale

Gli ambienti senza server sono soggetti a guasti transitori e invocazioni duplicate. I gestori di eventi devono essere progettati per idempotency. Conservare una finestra di deduplica [[]] (ad esempio, utilizzando DynamoDB TTL o un set Redis) che registra ID degli eventi elaborati. Se un evento arriva di nuovo all'interno della finestra, viene ignorato silenziosamente.

Quando un comando non riesce a far parte degli eventi che si verificano nel negozio, gli eventi sono già stati scritti. In tali casi, potrebbe essere necessario implementare un []compensando event[] (ad esempio, )]) per ripristinare lo stato. L'evento di compensazione viene memorizzato come un evento normale e innesca una proiezione che annulla il lavoro.

Inoltre, prendere in considerazione le code diad‐letter[ (DLQs) per eventi che ripetutamente non riescono a elaborare.

Ottimizzazione delle prestazioni e dei costi nei sistemi di eventi senza server

Mentre le scale serverless automaticamente, l'apprezzamento degli eventi di progettazione in modo spensierato può sostenere costi elevati.

  • Impiegazione di batch:[] Quando si proiettano eventi, leggere e scrivere in batch per minimizzare le richieste di database.
  • Snapshots:[] Archivia periodicamente le istantanee degli stati aggregati per evitare di rigiocare l'intero registro degli eventi su ogni lettura. Le snapshot vengono memorizzate nella stessa tabella dei negozi con una versione speciale (ad esempio, il numero di versione precedente da “SNAP”). La logica di rigioco inizia dall'ultima snapshot, riducendo drasticamente il tempo di lettura.
  • Caching:[[]] Cache ha spesso accesso ai dati del modello di lettura a livello di applicazione (ad esempio, utilizzando ElastiCache o CloudFront con contenuti dinamici).
  • Event partizionamento:[] Se si utilizza un sistema pub/sub come EventBridge, eventi di partizione per tipo aggregato per controllare il tasso di invocazione per le funzioni di proiezione.

Esempio: Strategia di snapshot in DynamoDB

Conservare un'istantanea con chiave di partizione = chiave aggregata e di ordine = "SNAP#[]]". Il carico di pagamento contiene lo stato ricostruito completo. Quando si recupera lo stato attuale, la domanda per gli eventi con la chiave di selezione maggiore della versione snapshot, riducendo il numero di eventi al processo.

Test e debug Sistemi senza server personalizzati per eventi

Le prove unità possono verificare i gestori dei comandi che producono gli eventi giusti dati input. I test di integrazione devono convalidare che le proiezioni aggiornino correttamente i modelli di lettura quando gli eventi vengono pubblicati. Poiché le funzioni serverless sono senza stato, considerare l'utilizzo di emulatori locali (ad esempio, AWS SAM locale, DynamoDB Local, EventBridge local testing library) per eseguire test in/CD pipeline.

Debugging problemi di produzione beneficia del registro eventi stesso - è possibile riprodurre eventi in un ambiente di sviluppo per ricreare la sequenza esatta che ha portato a un bug. Strumenti come [AWS X‐Ray o ]]]Azure Monitor]]] aiutano a tracciare invocazioni di funzione attraverso i servizi.

Pitfalls comune e come evitare di loro

  • Modalizzazione di dominio appropriato:[[] Non tutti i vantaggi di dominio aziendale dall'approvvigionamento di eventi. Se avete bisogno di CRUD semplice senza requisiti di audit, la testa sopraelevata potrebbe non essere giustificata.
  • Overly large events:[]] Memorizzazione di grandi carichi di pagamento (ad esempio, interi documenti) come un singolo evento riduce le prestazioni.
  • Progetto deriva:[] Quando i modelli di lettura diventano fuori dalla sincronizzazione a causa di eventi o bug mancati, è necessario un meccanismo di ripetizione.
  • Ignorando l'evoluzione dello schema:[ Gli eventi sono immutabili, ma i loro schemi cambiano. Utilizzare un registro e una versione ogni tipo di evento.
  • ]Cold inizia a interessare le proiezioni:[ Le funzioni di proiezione che vengono invocate raramente possono soffrire di una latenza di inizio freddo.

Esempio di architettura in tutto il mondo

Un'applicazione di trading finanziario costruita su AWS Lambda, DynamoDB e EventBridge ha implementato l'evento sourcing per registrare ogni ordine commerciale. Le funzioni di comando hanno gestito gli ordini di acquisto/vendita e hanno emesso [], ], e [] gli eventi.

Il team ha evitato i comuni inconvenienti, rafforzando la rigorosa versione dello schema degli eventi (utilizzando Apache Avro) e implementando un'apposita pipeline di rigioco che potrebbe ricostruire tutti i modelli letti da zero in meno di 30 minuti.

Conclusioni

Implementing Event Sourcing e CQRS in architetture serverless danno ai team di sviluppo la capacità di costruire sistemi altamente scalabili, controllabili e manutenbili. Grazie alla sua gestione completa dei servizi per lo storage degli eventi, il routing dei messaggi e la computazione, puoi concentrarti sulla logica aziendale mentre la piattaforma gestisce le preoccupazioni dell'infrastruttura.