Anziché archiviare solo lo stato corrente dei dati in un dominio, usare un archivio di solo accodamento per registrare la serie completa di azioni eseguite sui dati. L'archivio svolge la funzione di sistema di registrazione e consente di materializzare gli oggetti del dominio. Contribuisce quindi a semplificare le attività in domini complessi, evitando la necessità di sincronizzare il modello dati e il dominio aziendale e migliorando le prestazioni, la scalabilità e la velocità di risposta. Può inoltre garantire coerenza ai dati transazionali e mantenere dati di cronologia e audit trail completi che consentono di eseguire azioni di compensazione.
Contesto e problema
La maggior parte delle applicazioni interagisce con i dati e, in genere, le applicazioni mantengono lo stato corrente dei dati lasciando agli utenti la facoltà di aggiornarli. Ad esempio, nel modello tradizionale di creazione, lettura, aggiornamento ed eliminazione (CRUD) un processo di dati tipico consiste nel leggere i dati dall'archivio, apportare alcune modifiche e aggiornare lo stato corrente dei dati con i nuovi valori, spesso usando transazioni che bloccano i dati.
L'approccio CRUD presenta tuttavia alcune limitazioni:
I sistemi CRUD eseguono operazioni di aggiornamento direttamente su un archivio dati. Queste operazioni possono rallentare le prestazioni e la velocità di risposta e limitare la scalabilità, a causa del sovraccarico di elaborazione richiesto.
In un dominio in collaborazione con molti utenti simultanei, i conflitti di aggiornamento sono più frequenti poiché le operazioni di aggiornamento vengono eseguite su un unico elemento di dati.
A meno che non esista un altro meccanismo di controllo che registra i dettagli di ogni operazione in un log separato, la cronologia viene persa.
Soluzione
Il modello di origine eventi definisce un approccio alla gestione delle operazioni sui dati determinato da una sequenza di eventi, ognuno dei quali registrato in un archivio di solo accodamento. Il codice dell'applicazione invia una serie di eventi che descrivono in modo imperativo ogni azione eseguita sui dati nell'archivio eventi, in cui sono salvati in modo permanente. Ogni evento rappresenta un set di modifiche ai dati (ad esempio AddedItemToOrder
).
Gli eventi vengono salvati in modo permanente in un archivio eventi che svolge la funzione di sistema di registrazione (origine dati autorevole) dello stato corrente dei dati. L'archivio eventi pubblica in genere questi eventi in modo che i consumer vengano informati e, se necessario, possano gestirli. I consumer, ad esempio, possono iniziare le attività che si applicano alle operazioni sugli eventi di altri sistemi oppure eseguire qualsiasi altra azione associata necessaria per completare l'operazione. Tenere presente che il codice dell'applicazione che genera gli eventi è separato dai sistemi che sottoscrivono gli eventi.
Gli usi tipici degli eventi pubblicati dall'archivio eventi prevedono la gestione di viste materializzate di entità come azioni nell'applicazione e per l'integrazione con sistemi esterni. Un sistema, ad esempio, può gestire una vista materializzata di tutti gli ordini clienti usata per popolare parti dell'interfaccia utente. L'applicazione aggiunge nuovi ordini, aggiunge o rimuove gli articoli nell'ordine e aggiunge le informazioni di spedizione. Gli eventi che descrivono queste modifiche possono essere gestiti e usati per aggiornare la vista materializzata.
In qualsiasi momento, è possibile che le applicazioni leggano la cronologia degli eventi. È quindi possibile usarlo per materializzare lo stato corrente di un'entità riproducendo e utilizzando tutti gli eventi correlati a tale entità. Questo processo può verificarsi su richiesta per materializzare un oggetto dominio durante la gestione di una richiesta. In alternativa, il processo viene eseguito tramite un'attività pianificata in modo che lo stato dell'entità possa essere archiviato come vista materializzata, per supportare il livello di presentazione.
La figura illustra una panoramica del modello, incluse alcune delle opzioni relative all'uso del flusso eventi, tra cui la creazione di una vista materializzata, l'integrazione di eventi con applicazioni e sistemi esterni e la riproduzione di eventi per poter creare proiezioni dello stato corrente di specifiche entità.
Il modello di origine eventi offre i vantaggi seguenti:
Gli eventi non sono modificabili e possono essere memorizzati con un'operazione di solo accodamento. L'interfaccia utente, il flusso di lavoro o il processo che ha avviato un evento può continuare e le attività che gestiscono gli eventi possono essere eseguite in background. Questo processo, combinato con il fatto che non è presente alcuna contesa durante l'elaborazione delle transazioni, può migliorare notevolmente le prestazioni e la scalabilità per le applicazioni, soprattutto per il livello di presentazione o l'interfaccia utente.
Gli eventi sono oggetti semplici che descrivono alcune azioni che si sono verificate, insieme ai dati associati necessari per descrivere l'azione rappresentata dall'evento. Gli eventi non aggiornano direttamente un archivio dati. Vengono semplicemente registrati per essere gestiti al momento opportuno. L'uso degli eventi può semplificare l'implementazione e la gestione.
Gli eventi, in genere, sono facilmente comprensibili per gli esperti del settore, mentre la mancata corrispondenza di impedenza object-relational può rendere difficile la comprensione delle tabelle di database complesse. Le tabelle sono costrutti artificiali che rappresentano lo stato corrente del sistema, non gli eventi che si sono verificati.
La funzione di origine eventi contribuisce a impedire conflitti generati da aggiornamenti simultanei in quanto evita la necessità di aggiornare direttamente gli oggetti nell'archivio dati. La progettazione del modello di dominio, tuttavia, non prevede ancora la protezione da richieste che possono determinare uno stato incoerente.
L'archiviazione di sola accodamento degli eventi fornisce un audit trail che può essere usato per monitorare le azioni eseguite su un archivio dati. Può rigenerare lo stato corrente come viste materializzate o proiezioni riproducendo gli eventi in qualsiasi momento e può facilitare il test e il debug del sistema. Inoltre, il requisito di usare eventi di compensazione per annullare le modifiche può fornire una cronologia delle modifiche invertite. Questa funzionalità non sarebbe il caso se il modello archivia lo stato corrente. L'elenco di eventi può essere usato anche per analizzare le prestazioni dell'applicazione e per rilevare le tendenze di comportamento degli utenti. In alternativa, può essere usato per ottenere altre informazioni aziendali utili.
L'archivio eventi genera eventi e le attività eseguono operazioni in risposta agli eventi. È questa separazione tra le attività e gli eventi a garantire flessibilità ed estensibilità. Le attività sono a conoscenza del tipo di evento e dei dati relativi all'evento, ma non dell'operazione che l'evento ha generato. Ogni evento, inoltre, può essere gestito da più attività. Questo aspetto semplifica l'integrazione con altri servizi e sistemi che restano in ascolto solo di nuovi eventi generati dall'archivio eventi. Gli eventi che danno origine a nuovi eventi, tuttavia, tendono a essere di livello molto basso e può essere necessario generare eventi di integrazione specifici.
Il modello di origine eventi viene spesso combinato con il modello CQRS per l'esecuzione di attività di gestione dati in risposta agli eventi e per la materializzazione delle viste a partire da eventi archiviati.
Considerazioni e problemi
Prima di decidere come implementare questo modello, considerare quanto segue:
Il sistema acquisirà la coerenza finale solo durante la creazione di viste materializzate o la generazione di proiezioni di dati tramite la riproduzione di eventi. Si verifica un ritardo tra un'applicazione che aggiunge eventi all'archivio eventi come risultato della gestione di una richiesta, degli eventi pubblicati e dei consumer degli eventi che li gestiscono. Durante questo intervallo di tempo è possibile che siano giunti nell'archivio nuovi eventi che descrivono ulteriori modifiche alle entità. Il sistema deve essere progettato per tenere conto della coerenza finale in questi scenari.
Nota
Per altre informazioni sulla coerenza finale, vedere Nozioni di base sulla coerenza dei dati.
L'archivio eventi è un'origine permanente di informazioni e i dati dell'evento, quindi, non dovrebbero essere mai aggiornati. L'unico modo per aggiornare un'entità in modo da annullare una modifica è quello di aggiungere un evento di compensazione all'archivio eventi. Se deve essere modificato il formato, anziché i dati, degli eventi persistenti, ad esempio durante una migrazione, potrebbe essere difficile combinare eventi esistenti nell'archivio con la nuova versione. È possibile che sia necessario scorrere tutti gli eventi per modificarli in modo da uniformarli al nuovo formato oppure aggiungere nuovi eventi che usano il nuovo formato. Valutare l'opportunità di usare uno stamp di versione in ogni versione dello schema eventi per mantenere sia il nuovo che il vecchio formato.
È possibile che nell'archivio eventi vengano archiviati eventi anche da parte di applicazioni multithread e da più istanze delle applicazioni. La coerenza degli eventi nell'archivio eventi è di fondamentale importanza, così come l'ordine degli eventi che interessano un'entità specifica (l'ordine in cui vengono apportate le modifiche influisce sullo stato corrente dell'entità). L'aggiunta di un timestamp a ogni evento contribuisce a evitare problemi. Un'altra procedura comune è quella di annotare ogni evento generato da una richiesta con un identificatore incrementale. Se due azioni tentano contemporaneamente di aggiungere eventi per una stessa entità, l'archivio eventi può rifiutare un evento che corrisponde a un identificatore di entità e a un identificatore di evento esistenti.
Non esiste un approccio standard o un meccanismo esistente, ad esempio query SQL, per leggere gli eventi in modo da ottenere informazioni. Un flusso di eventi è l'unico tipo di dati che è possibile estrarre usando un identificatore di eventi come criterio. L'ID evento è generalmente associato a entità individuali. Lo stato corrente di un'entità può essere determinato solo riproducendo tutti gli eventi associati all'entità sullo stato originale dell'entità.
La lunghezza di ogni flusso di eventi influisce sulle modalità di gestione e di aggiornamento del sistema. Se i flussi sono di grandi dimensioni, valutare l'opportunità di creare snapshot a intervalli specifici, ad esempio dopo un determinato numero di eventi. È possibile ottenere lo stato corrente dell'entità mediante lo snapshot e la riproduzione di tutti gli eventi che si sono verificati a partire da quel punto nel tempo. Per altre informazioni sulla creazione di snapshot di dati, vedere Replica snapshot secondaria primaria.
Anche se l'origine eventi riduce al minimo il rischio di conflitti tra gli aggiornamenti ai dati, l'applicazione deve comunque essere in grado di gestire eventuali incoerenze derivanti dalla coerenza finale e dalla mancanza di transazioni. Ad esempio, un evento che indica una riduzione dell'inventario delle scorte potrebbe arrivare nell'archivio dati mentre viene effettuato un ordine per l'articolo. Questa situazione comporta un requisito per riconciliare le due operazioni, consigliando il cliente o creando un ordine indietro.
La pubblicazione degli eventi potrebbe essere almeno una volta e pertanto i consumer degli eventi devono essere idempotenti. In questo modo, non è necessario riapplicare l'aggiornamento descritto in un evento se l'evento viene gestito più volte. Più istanze di un consumer possono gestire e aggregare la proprietà di un'entità, ad esempio il numero totale di ordini effettuati. Solo uno deve avere esito positivo nell'incremento dell'aggregazione, quando si verifica un evento inserito nell'ordine. Anche se questo risultato non è una caratteristica chiave dell'origine degli eventi, è la solita decisione di implementazione.
L'archiviazione eventi selezionata deve supportare il carico di eventi generato dall'applicazione.
Tenere presente gli scenari in cui l'elaborazione di un evento comporta la creazione di uno o più nuovi eventi, perché ciò può causare un ciclo infinito.
Quando usare questo modello
Usare questo modello negli scenari seguenti:
Quando si vuole acquisire l'intento, lo scopo o il motivo insito nei dati. Ad esempio, le modifiche apportate a un'entità cliente possono essere acquisite come una serie di tipi di eventi specifici, ad esempio Moved home, Closed account o Defunto.
Quando è essenziale ridurre al minimo o evitare completamente la presenza di conflitti tra gli aggiornamenti ai dati.
Quando si desidera registrare gli eventi che si verificano, per riprodurre gli eventi per ripristinare lo stato di un sistema, per eseguire il rollback delle modifiche o per mantenere una cronologia e un log di controllo. Ad esempio, quando un'attività prevede più passaggi, potrebbe essere necessario eseguire azioni per ripristinare gli aggiornamenti e quindi riprodurre alcuni passaggi per riportare i dati in uno stato coerente.
Quando si usano eventi. Si tratta di una caratteristica naturale del funzionamento dell'applicazione e richiede poco lavoro di sviluppo o implementazione aggiuntivo.
Quando è necessario separare il processo di input o aggiornare i dati dalle attività necessarie per applicare queste azioni. Questa modifica potrebbe essere quella di migliorare le prestazioni dell'interfaccia utente o di distribuire eventi ad altri listener che eseguono azioni quando si verificano gli eventi. Ad esempio, è possibile integrare un sistema di retribuzione con un sito Web per l'invio di spese. Gli eventi generati dall'archivio eventi in risposta agli aggiornamenti dei dati effettuati nel sito Web vengono utilizzati sia dal sito Web che dal sistema di retribuzione.
Quando si vuole che la flessibilità sia in grado di modificare il formato dei modelli materializzati e dei dati delle entità se i requisiti cambiano o, se usati con CQRS, è necessario adattare un modello di lettura o le visualizzazioni che espongono i dati.
Quando viene usato con CQRS e la coerenza finale è accettabile durante l'aggiornamento di un modello di lettura o l'impatto sulle prestazioni delle entità e dei dati riattivati da un flusso di eventi è accettabile.
Questo modello può non essere utile nelle situazioni seguenti:
Domini semplici o di piccole dimensioni, con poca o alcuna logica di business o sistemi non di dominio che per natura interagiscono bene con i tradizionali meccanismi di gestione dati CRUD.
Sistemi in cui sono necessari coerenza e aggiornamenti in tempo reale alle visualizzazioni dei dati.
I sistemi in cui non sono necessari audit trail, cronologia e funzionalità per eseguire il rollback e la riproduzione delle azioni.
Sistemi in cui è presente solo una bassa occorrenza di aggiornamenti in conflitto ai dati sottostanti. Ad esempio, sistemi che in prevalenza aggiungono dati anziché aggiornarli.
Progettazione del carico di lavoro
Un architetto deve valutare il modo in cui il modello di origine eventi può essere usato nella progettazione del carico di lavoro per soddisfare gli obiettivi e i principi trattati nei pilastri di Azure Well-Architected Framework. Ad esempio:
Concetto fondamentale | Come questo modello supporta gli obiettivi di pilastro |
---|---|
Le decisioni di progettazione dell'affidabilità consentono al carico di lavoro di diventare resilienti a malfunzionamenti e di assicurarsi che venga ripristinato in uno stato completamente funzionante dopo che si verifica un errore. | A causa dell'acquisizione di una cronologia delle modifiche in un processo aziendale complesso, può facilitare la ricostruzione dello stato se è necessario ripristinare gli archivi di stato. - PARTIZIONAMENTO dei dati RE:06 - RE:09 Ripristino di emergenza |
L'efficienza delle prestazioni consente al carico di lavoro di soddisfare in modo efficiente le richieste tramite ottimizzazioni in termini di scalabilità, dati, codice. | Questo modello, in genere combinato con CQRS, una progettazione di dominio appropriata e snapshot strategico, può migliorare le prestazioni del carico di lavoro a causa delle operazioni di sola accodamento atomiche e l'evitare il blocco del database per scritture e letture. - Prestazioni dei dati PE:08 |
Come per qualsiasi decisione di progettazione, prendere in considerazione eventuali compromessi rispetto agli obiettivi degli altri pilastri che potrebbero essere introdotti con questo modello.
Esempio
Un sistema di gestione conferenze deve tenere traccia del numero di prenotazioni completate per una conferenza. In questo modo è possibile verificare se sono ancora disponibili postazioni, quando un potenziale partecipante tenta di effettuare una prenotazione. Il sistema può archiviare il numero totale di prenotazioni per una conferenza in almeno due modi:
Il sistema può memorizzare le informazioni sul numero totale di prenotazioni come entità separata in un database in cui sono contenute le informazioni di prenotazione. Per ogni nuova prenotazione effettuata o annullata, il sistema può incrementare o diminuire il numero di conseguenza. Questo approccio è molto semplice in teoria, ma può generare problemi di scalabilità nel caso in cui un numero elevato di partecipanti tenti di effettuare una prenotazione in un breve intervallo di tempo, ad esempio nell'ultimo giorno prima della chiusura delle prenotazioni.
Il sistema può memorizzare le informazioni su ogni nuova prenotazione aggiunta o annullata come eventi contenuti in un archivio eventi. Può quindi calcolare il numero di posti disponibili riproducendo questi eventi. Questo approccio può essere più scalabile grazie alla non modificabilità degli eventi. Il sistema deve solo riuscire a leggere dati dall'archivio eventi e, se necessario, aggiungere nuovi dati. Le informazioni sugli eventi rappresentati da ogni nuova prenotazione aggiunta o annullata non vengono mai modificate.
Il grafico seguente illustra come il sottosistema di prenotazione dei posti del sistema di gestione delle conferenze possa essere implementato usando il modello di origine eventi.
Di seguito è illustrata la sequenza di azioni per la prenotazione di due posti:
L'interfaccia utente esegue un comando per prenotare i posti per due partecipanti. Il comando viene gestito da un gestore di comando separato. Una parte della logica che viene separata dall'interfaccia utente diventa responsabile della gestione delle richieste inviate sotto forma di comandi.
Viene creata un'aggregazione contenente informazioni su tutte le prenotazioni per la conferenza tramite l'esecuzione di query sugli eventi che descrivono le nuove prenotazioni e gli annullamenti. L'aggregazione prende il nome di
SeatAvailability
ed è contenuta in un modello di dominio che espone i metodi per l'esecuzione di query e la modifica dei dati inclusi nell'aggregazione.Alcune ottimizzazioni da considerare usano snapshot (in modo che non sia necessario eseguire query e riprodurre l'elenco completo degli eventi per ottenere lo stato corrente dell'aggregazione) e mantenere una copia memorizzata nella cache dell'aggregazione in memoria.
Il gestore di comando richiama un metodo esposto dal modello di dominio per effettuare le prenotazioni.
L'aggregazione
SeatAvailability
registra un evento contenente il numero di posti prenotati. La volta successiva in cui l'aggregazione applica un evento verranno usate tutte le prenotazioni per calcolare il numero di posti rimanenti.Il sistema aggiunge il nuovo evento all'elenco degli eventi presenti nell'archivio eventi.
Se un utente annulla una prenotazione, il sistema segue un processo simile, ma in questo caso il gestore di comando esegue un comando che genera un evento di annullamento della prenotazione e lo aggiunge all'archivio eventi.
Oltre a fornire più ambito per la scalabilità, l'uso di un archivio eventi fornisce anche una cronologia completa, o audit trail, delle prenotazioni e degli annullamenti per una conferenza. Gli eventi nell'archivio di eventi costituiscono il record esatto. Non è necessario rendere persistenti le aggregazioni in altro modo perché il sistema può riprodurre facilmente gli eventi e ripristinare lo stato in qualsiasi momento.
Altre informazioni su questo esempio sono disponibili in Introducing Event Sourcing (Introduzione all'origine eventi).
Passaggi successivi
Nozioni di base sulla coerenza dei dati. Quando si usa l'origine eventi con un archivio di lettura o viste materializzate separate, i dati di lettura non saranno immediatamente coerenti. I dati saranno invece coerenti solo alla fine. Questo articolo riepiloga i problemi relativi alla gestione della coerenza sui dati distribuiti.
Linee guida di partizionamento di dati. I dati vengono spesso partizionati quando si usa l'origine eventi per migliorare la scalabilità, ridurre i conflitti e ottimizzare le prestazioni. Questo articolo descrive come dividere i dati in partizioni discrete e i problemi che possono verificarsi.
Blog di Martin Fowler:
Risorse correlate
Per l'implementazione di questo modello possono risultare utili i modelli e le informazioni aggiuntive seguenti:
Modello di separazione di responsabilità per query e comandi (CQRS, Command and Query Responsibility Segregation). L'archivio di scrittura che costituisce un'origine permanente di informazioni per l'implementazione di un modello CQRS è spesso basato su un'implementazione del modello di origine eventi. Questo modello descrive come isolare in un'applicazione le operazioni di lettura dei dati dalle operazioni di aggiornamento dei dati tramite l'uso di interfacce separate.
Modello di vista materializzata. L'archivio dati usato in un sistema basato sull'origine eventi in genere non è adatto per eseguire query efficienti. In questo caso, l'approccio comune è quello di generare viste dati prepopolate a intervalli regolari o in caso di variazione dei dati.
Modello di transazioni di compensazione. I dati esistenti in un archivio di origine eventi non vengono aggiornati. Vengono invece aggiunte nuove voci che passano lo stato delle entità ai nuovi valori. Per invertire una modifica, vengono usate voci di compensazione perché non è possibile invertire la modifica precedente. In questo modello viene descritto come annullare le conseguenze prodotte da un'operazione precedente.