Click here to read it in English.
Nell’ambito dei microservizi, uno dei problemi che spesso ci troviamo ad affrontare è la necessità di garantire la consistenza dei dati, aggiornati in parallelo da attori diversi.
In questo contesto, si sta affermando il pattern architetturale Saga come pratica di successo: scopriamo come funziona e quali problematiche va a risolvere.
Consistenza dei dati: cos’è e quali problematiche implica?
Garantire la consistenza dei dati vuol dire assicurarsi che i dati siano significativamente ed effettivamente utilizzabili nelle applicazioni aziendali.
Un esempio di consistenza dei dati proviene dai contesti e-commerce: dopo il pagamento di un ordine, sarà necessario aggiornare sia la tabella relativa ai pagamenti sia quella relativa agli ordini, per fare in modo che l’ordine risulti pagato e possa essere presa in carico per la spedizione; tali aggiornamenti, potenzialmente provenienti da fonti diverse, devono comunque preservare la coerenza dei dati.
Preservare la consistenza del dato è una problematica delicata da affrontare.
In particolare, ci sono due punti di attenzione che devono essere presi in considerazione in situazioni di questo tipo, ovvero:
- assicurare la consistenza dei dati in un’operazione distribuita tra sistemi diversi;
- garantire error safety.
Per garantire la consistenza dei dati è necessario assicurarsi che il flusso di esecuzione delle varie operazioni su diversi microservizi rispetti un certo ordine, ciò implica quindi garantire error safety, ovvero la presenza di un meccanismo di remediation che ripristini la situazione corretta in caso di errore.
Facendo un esempio concreto, analizziamo un moderno servizio di food delivery: le operazioni che servono a “portare avanti” un’ordine sono molteplici e, molto spesso, coinvolgono diversi attori, ognuno dei quali effettua una o più operazioni relative all’ordine, come nel caso d’uso a seguire.
- L’utente clicca su “Ordina”;
- L’ordine viene creato;
- Il servizio di pagamento fa in modo che l’utente possa pagare l’ordine;
- L’utente paga l’ordine;
- Il servizio di pagamento aggiorna l’ordine;
- Il servizio degli ordini notifica il ristorante;
- Il ristorante conferma ed immette un orario per il ritiro;
- Il servizio di delivery notifica il rider;
- Il rider prende l’ordine e lo consegna;
- L’ordine viene chiuso.
Non è sempre facile, con tutti gli step e gli attori sopra elencati, garantire la consistenza dei dati in tutti i punti ed evitare che ci siano errori lungo questa catena.
È proprio qui che può venire in aiuto un pattern di gestione dei dati che si sta affermando sempre di più come risposta prediletta in questo genere di situazioni: il Saga Pattern.
Che cos’è il Saga Pattern
L’idea alla base del Saga Pattern, come descritto nel paragrafo precedente, è la gestione di transazioni distribuite, dall’inizio alla fine, ognuna delle quali è, appunto, una saga.
Si tratta di un pattern il cui scopo è garantire l’ordine di esecuzione delle transazioni che fanno parte della stessa saga, ovvero che devono essere eseguite in un ordine prestabilito e il cui effetto non è compromesso da eventuali transazioni intermedie appartenenti a saghe diverse.
Questo pattern aiuta quindi a gestire la consistenza dei dati nell’esecuzione di transazioni distribuite tra microservizi diversi; coinvolge diversi attori (servizi) che vanno ad agire su una stessa entità tramite singole transazioni atte all’aggiornamento di un dato comune.
L’obiettivo è duplice: mantenere l’identità del dato ed effettuare azioni di compensazione per ripristinarla in caso di errore.
Confronto tra differenti approcci Saga Pattern
Esistono principalmente due approcci per gestire il pattern in esame:
- events/choreography → nessun coordinatore, i servizi portano avanti la saga senza avere qualcuno che li controlli, ma lavorando insieme
- commands/orchestration → controllo centralizzato, gestito da un orchestratore
Entrambi gli approcci sopra citati hanno pro e contro, e chiaramente non esiste una regola universale che stabilisca se si debba usare un approccio piuttosto che un altro: dipende sempre dal progetto da realizzare.
Approccio Events/Choreography
L’approccio events/choreography prevede che tutti i microservizi coinvolti lavorino innescati da eventi specifici, come in una vera e propria coreografia.
Non esiste infatti, in questo approccio, un controllore che si occupi di amministrare tutto, ma ogni servizio sa esattamente cosa fare e quando farlo; l’unione, infatti, del “lavoro” di tutti i servizi sfocia nell’inizio, nell’avanzamento e nella conclusione della saga.
Questa collaborazione avviene tramite lo scambio di messaggi - gli eventi - tra i microservizi, solitamente usando un Message Broker per questo scopo.
In questo modo si ha una logica decentralizzata, distribuita tra i vari servizi che si occupano della saga.
Riprendendo l’esempio di un ordine su un sistema di food delivery, la coreografia di eventi potrebbe essere la seguente:
- order-service: ordine aperto → invio evento
ordine aperto - payment-service: ricezione evento di ordine aperto → elaborazione pagamento → invio evento ordine pagato
- delivery-service: ricezione evento di ordine pagato → invio ordine all’indirizzo dell’utente → invio evento ordine inviato
- ....
>Come si nota nel caso d’uso sopra riportato, ogni servizio conosce solo ed esattamente il suo environment, sa cosa fare e quando farlo, e la saga avanza in modo naturale nonostante siano coinvolti microservizi diversi, che fanno cose diverse: essi non hanno bisogno di comunicare direttamente, nonostante eseguano una sequenza di attività indirettamente legate tra loro.
Questo approccio ha diversi vantaggi:
- non c’è una forte dipendenza con un componente centrale → non c’è un Single point of failure;
- è l’ideale per transazioni che coinvolgono pochi attori, in quanto semplice e veloce da sviluppare;
- Agile prone: ogni team può lavorare sul proprio servizio a prescindere dagli altri team/microservizi (una volta concordati il formato e la nomenclatura dei messaggi/eventi).
Tuttavia, presenta anche alcuni svantaggi:
- non è adatto a gestire saghe complesse:
- è un sistema che, con l’aggiunta di nuovi servizi, tende a diventare velocemente più complesso
- all’aumentare del numero di servizi coinvolti, risulta più difficile gestire il flusso: si rischia di non capire quale servizio gestisce uno specifico evento e, di conseguenza, testare il sistema diventa più complesso;
- se ci sono tanti attori coinvolti che arricchiscono una base di dati comune può succedere che non si abbia sempre pieno controllo del dato, se non si utilizza un attore preposto ad assicurarne la persistenza;
- in mancanza di un punto in cui si ha una “visione d’insieme” della saga, aumenta il rischio di cicli tra le dipendenze.
Approccio Commands/Orchestration
Questo approccio, al contrario del precedente, prevede l’utilizzo di un servizio centrale che gestisce e controlla tutto il flusso della saga. Il servizio è, come si intuisce dal nome dell’approccio, l’orchestratore.
L’orchestratore ha il ruolo di controllore, con cui si interfacciano tutti gli attori coinvolti nella transazione.
Secondo questo approccio è l’orchestratore a dirigere il flusso, inviando dei comandi sotto forma di messaggi tramite il Message Broker, diretti ad uno specifico servizio. Questo, alla ricezione di tale comando, attuerà le proprie logiche per portare avanti la saga e risponderà inviando un evento sul Message Broker, che sarà gestito dall’orchestratore.
In questo modo l’orchestratore sarà in grado di portare avanti la saga dirigendo la sua “orchestra” di servizi coinvolti.
L’esempio di ordine su un sistema di food delivery potrebbe essere, questa volta, il seguente:
- order-service: ordine aperto → invio evento di ordine aperto
- orchestrator: ricezione evento ordine aperto → invio comando di tentativo di pagamento
- payment-service: ricezione evento di tentativo di pagamento → elaborazione pagamento → invio evento di ordine pagato
- orchestrator: ricezione evento ordine pagato → invio comando di spedizione ordine
- delivery-service: ricezione comando di spedizione ordine → invio ordine all’indirizzo dell’utente → invio evento di ordine inviato
- …
Anche in questo caso i servizi conoscono solo il loro environment ed implementano quella piccola parte di logica legata alla saga; tuttavia, hanno questa volta una sorta di punto di riferimento, come si evince dallo schema a seguire.
I vantaggi di questo approccio:
- avere un punto centrale
che fornisce una visione d’insieme aiuta ad evitare cicli tra i servizi, ovvero la possibilità che N servizi si chiamino ciclicamente tra loro creando un loop (e.g. Service A → Service B → Service C → Service A...)
- la logica di business è concentrata in un unico punto, di conseguenza:
- può essere gestita e modificata agilmente
- l’aggiunta di un nuovo step comporta, dopo aver implementato la funzionalità in maniera indipendente, la sola modifica dell’orchestratore per aggiornare il flusso della saga, operazione trasparente per gli altri servizi
- Agile prone: come per l’approccio choreography, anche in questo caso i servizi sono separati tra loro e i team possono lavorare in modo indipendente (una volta concordati il formato e la nomenclatura dei messaggi/eventi);
- gestione dei rollback più semplice.
Gli svantaggi invece sono i seguenti:
- la logica centralizzata può rappresentare anche un rischio, poiché si ha un single point of failure. In questo modo, se l’orchestratore non funziona, tutto il flusso si blocca;
- è necessario implementare un servizio aggiuntivo, oltre ai servizi di gestione della saga: l’orchestratore;
- il numero di interazioni è esattamente duplicato: ad ogni messaggio (event) dell’approccio choreography corrispondono due messaggi (command + event) nell’approccio orchestrator.
Quale approccio scegliere?
Nel capitolo precedente abbiamo descritto i pro e i contro di entrambi gli approcci, events/choreography e commands/orchestration. Quale scegliere dipende, ovviamente, dal caso specifico.
Ad esempio, potrebbe essere svantaggioso sviluppare un orchestrator per gestire una saga che coinvolge due microservizi ed un flusso molto semplice: il valore aggiunto ottenuto dall’introduzione di un orchestratore sarebbe annullato da un considerevole aumento della complessità, soprattutto iniziale, legata all’approccio commands/orchestrator.
Quale approccio abbiamo scelto
In Mia‑Platform ci siamo trovati davanti alla scelta dell’approccio che più facesse al caso nostro.
Analizzando le necessità, i vari step del flusso della saga e gli attori coinvolti abbiamo optato per l’approccio Commands/Orchestration e, poiché sul web non abbiamo trovato implementazioni ready-to-use di un orchestratore, abbiamo deciso di arricchire la piattaforma sviluppando un nuovo componente: il Flow Manager.
Questo articolo prosegue con un approfondimento su Flow Manager, l'orchestratore di Saga di Mia‑Platform.
Articolo scritto da Francesco Francomano, Senior Full Stack Developer, e Giuseppe Manzi, Full Stack Specialist.
© MIA s.r.l. Tutti i diritti riservati