La lezione 73 si è chiusa sul monolite modulare, e sull’idea che per la maggior parte dei team la forma di default del backend sia un singolo deployable con confini interni forti. Questa lezione parte dalla domanda successiva: quando hai effettivamente più servizi (siano due o duecento), come dovrebbero parlarsi tra loro? La risposta sincrona via RPC (il servizio A chiama il servizio B via HTTP, aspetta la risposta, restituisce al chiamante) è il default che spedisci per primo, e spesso quello che richiama il team sei mesi dopo con una lista di failure mode. L’event-driven architecture è il pattern alternativo, e l’industria ha passato un decennio a imparare dove funziona e dove no.
In sintesi: nell’event-driven architecture, i servizi non si chiamano tra loro. Emettono eventi. Altri servizi si iscrivono e reagiscono. L’event bus (spesso Kafka, spesso un equivalente managed) sta in mezzo e disaccoppia i producer dai consumer.
I benefici sono reali. I trade-off sono diversi rispetto all’RPC sincrono, non strettamente migliori, e la scelta tra i due modi di coordinare workflow tra servizi (choreography e orchestration) è la seconda decisione che il team deve prendere una volta che si è impegnato sugli eventi. Questa lezione copre il pattern, le due varianti, il pattern saga che spesso si implementa sopra, e il toolset 2026 che è convergente attorno ad esso.
L’RPC sincrono e quanto costa
Partiamo dal failure mode contro cui il pattern event-driven sta reagendo. In un backend basato su RPC sincrono, una richiesta dell’utente colpisce il servizio A, che chiama il servizio B, che chiama il servizio C, che chiama il servizio D. Ogni chiamata è una richiesta HTTP o gRPC. Ogni chiamata blocca finché il servizio successivo non risponde. L’intera catena di chiamate resta aperta mentre la richiesta cammina attraverso il sistema.
I problemi si accumulano:
La latenza si somma. La latenza visibile all’utente è la somma di ogni hop. 50ms di p95 per ognuno dei quattro servizi diventano 200ms di p95 end-to-end, prima ancora di rete e database.
La tail latency domina. Se il servizio D ha un 99esimo percentile di 500ms, e la tua richiesta si dirama in molte chiamate di questo tipo, il tuo p99 end-to-end è molto peggio del p99 di un singolo servizio.
I fallimenti cascano. Quando il servizio D è down, il servizio C va in timeout. Quando C va in timeout, B va in timeout. L’intera catena è giù perché l’anello più profondo è giù. Circuit breaker e retry aiutano; non eliminano l’accoppiamento.
Accoppiamento al deploy time. Il servizio A conosce l’indirizzo del servizio B e la forma della sua API. Quando B cambia, A deve essere aggiornato. Moltiplica per N servizi.
Questi non sono bug dell’RPC sincrono; sono le conseguenze del modello. Il pattern ha il suo posto, e la maggior parte dei read path rivolti all’utente è più facile da ragionarci come RPC. Ma per i write path e per i workflow tra servizi, il costo si accumula.
La forma event-driven
L’alternativa: il servizio A emette un evento (“OrderPlaced”, con i dettagli dell’ordine nel payload) e ritorna al suo chiamante. I servizi B, C e D si sono iscritti agli eventi OrderPlaced. Ognuno consuma l’evento e fa il proprio lavoro in modo asincrono. La richiesta visibile all’utente finisce in A; il resto succede in background.
Il substrato è solitamente un log durabile: Kafka (lezione 42), Pulsar, AWS Kinesis, Google Pub/Sub, Azure Event Hubs. Il producer scrive una volta. Molti consumer leggono indipendentemente. Il log persiste gli eventi così che un consumer lento o temporaneamente down possa recuperare in seguito. Le delivery semantics (lezione 16) dettano il resto del contratto.
I benefici invertono i failure mode dell’RPC sincrono:
La latenza per l’utente è solo la latenza del servizio A. Il lavoro a valle accade fuori banda.
Isolamento del fallimento. Il fatto che il servizio D sia down è un problema di D. Il servizio A torna all’utente normalmente; gli eventi si accodano nel log; il servizio D recupera quando torna su.
Accoppiamento lasco. Il servizio A non sa chi consuma OrderPlaced. Si possono aggiungere nuovi consumer senza cambiare A. Si possono rimuovere vecchi consumer senza cambiare A.
Tracciabilità. L’event log è un registro di tutto ciò che è successo, in ordine. Riprodurre il log ricostruisce lo stato. Il debug ripercorre gli eventi.
Anche i trade-off sono reali:
Eventual consistency. Lo stato attraverso il sistema è consistente alla fine, non immediatamente. L’utente vede il suo ordine piazzato; la mail di conferma arriva un secondo dopo. Per la maggior parte dei workflow va bene. Per alcuni no.
Debug più difficile. “Cosa succede quando un ordine viene piazzato?” non è più rispondibile leggendo il codice del servizio A. La risposta è sparsa fra ogni consumer di OrderPlaced.
Schema evolution. Lo schema dell’evento è ora un contratto tra servizi. Cambiarlo è un cambiamento coordinato.
Substrato operativo. Far girare un cluster Kafka (o il suo equivalente managed) è una disciplina operativa a sé.
Il pattern vale il costo quando il workflow è naturalmente asincrono, quando il lavoro a valle può fallire e fare retry indipendentemente, e quando il team è disposto a investire nel substrato. È esagerato per il caso semplice in cui una richiesta ha bisogno solo di una scrittura in database e di una risposta.
Choreography: i servizi reagiscono
Una volta che gli eventi sono il substrato, hai due modi per esprimere un workflow multi-step. Il primo è la choreography. Ogni servizio conosce il proprio compito. Quando vede un evento di cui gli importa, fa il suo lavoro ed emette il proprio evento. Non c’è coordinatore centrale. Il workflow è il comportamento emergente di servizi che reagiscono l’uno all’altro.
Un esempio canonico: un workflow di piazzamento di un ordine.
sequenceDiagram
participant U as User
participant O as OrderService
participant K as Kafka
participant P as PaymentService
participant I as InventoryService
participant N as NotificationService
U->>O: POST /orders
O->>K: emit OrderPlaced
O-->>U: 201 Created
K->>P: OrderPlaced
P->>K: emit PaymentAuthorized
K->>I: PaymentAuthorized
I->>K: emit InventoryReserved
K->>N: InventoryReserved
N->>N: send confirmation email
L’order service non sa nulla di pagamenti, inventario o notifiche. Emette OrderPlaced e ritorna. I servizi a valle si iscrivono e reagiscono. Ogni servizio è piccolo, focalizzato e deployabile in modo indipendente.
I punti di forza della choreography:
Accoppiamento lasco. Aggiungere un nuovo partecipante (analytics, fraud detection, notifiche ai partner) è una questione di iscriversi agli eventi rilevanti. Nessun servizio esistente cambia.
Nessun single point of failure per il workflow. Ogni servizio è indipendente.
Adatta in modo naturale a reazioni genuinamente indipendenti. Quando N servizi devono reagire allo stesso evento in parallelo e non dipendono l’uno dall’altro, la choreography è la forma ovvia.
I punti deboli:
Nessuna fonte unica di verità per il workflow. Chiedere “cosa succede quando un ordine viene piazzato” richiede di leggere il sorgente di ogni consumer.
Difficile vedere “dov’è bloccato l’ordine #123?”. L’ordine è qualunque cosa gli eventi dicano che sia. Il debug di un ordine bloccato significa leggere i log di più servizi e ricostruire lo stato dalla sequenza degli eventi.
Dipendenze implicite. Il servizio C dipende dal fatto che B emetta PaymentAuthorized. Se B cambia il nome dell’evento o il payload, C si rompe. La dipendenza è reale ma invisibile nel codice.
La logica di compensazione è sparpagliata. Quando il workflow deve fare rollback (pagamento autorizzato ma inventario non disponibile), la compensazione deve essere coordinata fra servizi che non si conoscono tra loro.
Orchestration: un coordinatore guida
L’alternativa è l’orchestration. Un coordinatore centrale (un orchestrator, un saga manager, un workflow engine) tiene la definizione del workflow e dice a ciascun servizio cosa fare, in ordine, con la gestione esplicita dei fallimenti e delle compensazioni.
Lo stesso workflow di piazzamento dell’ordine come flusso orchestrato:
sequenceDiagram
participant U as User
participant O as OrderService
participant W as WorkflowEngine
participant P as PaymentService
participant I as InventoryService
participant N as NotificationService
U->>O: POST /orders
O->>W: start OrderWorkflow
O-->>U: 201 Created
W->>P: AuthorizePayment
P-->>W: success
W->>I: ReserveInventory
I-->>W: success
W->>N: SendConfirmation
N-->>W: success
W->>W: workflow complete
Il workflow engine possiede lo stato del workflow. Conosce lo step corrente, lo step successivo, e cosa fare in caso di fallimento. I servizi partecipanti sono passivi: espongono azioni, e l’orchestrator le invoca.
I punti di forza dell’orchestration:
Fonte unica di verità. La definizione del workflow è un singolo pezzo di codice o configurazione che cattura l’intero flusso.
Visibilità. L’UI dell’orchestrator mostra dov’è ogni workflow. “Dov’è bloccato l’ordine #123?” è una query verso l’orchestrator, non un esercizio forense.
Gestione esplicita degli errori e compensazione. La definizione del workflow dichiara cosa succede in caso di fallimento. Riprova questo step. Compensa quell’altro. Escala a un umano dopo tre fallimenti.
Ragionare più facilmente su workflow complessi. Quando il workflow ha dieci step con branch condizionali e timeout, l’orchestration rende la struttura visibile. La choreography la nasconde.
I punti deboli:
L’orchestrator è un servizio critico. Se va giù, nessun workflow procede. I deployment in produzione lo rendono altamente disponibile, ma la responsabilità operativa è reale.
Accoppiamento più stretto tra orchestrator e partecipanti. L’orchestrator conosce i partecipanti per nome e indirizzo. Aggiungere un nuovo partecipante significa cambiare la definizione del workflow.
Un nuovo pezzo di infrastruttura da far girare. La choreography ha bisogno solo dell’event bus. L’orchestration ha bisogno dell’orchestrator in più.
La scelta tra choreography e orchestration non è assoluta. La maggior parte dei sistemi maturi usa entrambe: choreography per i casi a basso accoppiamento (analytics, audit, notifiche broadcast), orchestration per i workflow di business multi-step complessi (processing degli ordini, onboarding degli account, valutazione delle richieste di indennizzo).
Il pattern saga
Il pattern saga è la soluzione canonica al problema che il 2PC (lezione 15) prova e fallisce a risolvere su scala: come coordini una transazione che attraversa più servizi, ognuno con il proprio database, quando il distributed locking e il 2PC sono troppo costosi?
Una saga esprime la transazione long-running come una sequenza di transazioni locali, una per servizio, con una compensazione definita per ognuna. Se lo step tre fallisce, la saga esegue le compensazioni per gli step uno e due in ordine inverso, lasciando il sistema in uno stato consistente (anche se non identico).
La saga nell’esempio dell’ordine:
- AuthorizePayment. Compensazione: VoidAuthorization.
- ReserveInventory. Compensazione: ReleaseReservation.
- SendConfirmation. (Nessuna compensazione necessaria; un’email in più è innocua.)
Se ReserveInventory fallisce, la saga esegue VoidAuthorization. L’utente vede un rifiuto dell’ordine; il pagamento non viene addebitato; il sistema è consistente.
La saga si può implementare in entrambi i modi. Come choreography: ogni servizio emette un evento di successo quando il suo step riesce e un evento di fallimento quando fallisce; i servizi a valle compensano quando vedono un fallimento. Come orchestration: l’orchestrator esegue gli step in ordine e invoca esplicitamente le compensazioni in caso di fallimento.
La saga orchestrata è il pattern dominante nel 2026 perché la visibilità dell’orchestrator rende lo stato della saga debuggabile, che è la parte della saga che fa più male in produzione.
Il toolset 2026
Il tooling per l’orchestration si è consolidato. I quattro contendenti seri per l’orchestrazione di workflow guidata da codice:
Temporal è la scelta dominante per i workflow definiti in codice. I workflow sono scritti come codice ordinario (Go, Java, TypeScript, Python) e Temporal li rende durabili: lo stato è checkpointato automaticamente, i restart riprendono dall’ultimo checkpoint, retry e timeout sono first-class. Il modello mentale è “scrivi il workflow come una funzione, e Temporal gestisce i fallimenti”. Temporal Cloud è l’offerta managed.
AWS Step Functions è la risposta nativa AWS. I workflow si definiscono in Amazon States Language (un DSL JSON) e girano dentro l’account AWS. L’integrazione stretta con il resto di AWS (Lambda, ECS, SQS, DynamoDB) è il principale punto di richiamo. Lo scambio è il lock-in ad AWS.
Camunda è la variante business-process. I workflow si definiscono in BPMN (Business Process Model and Notation), lo standard XML che viene dal mondo dei business process. Il pubblico sono i team in cui i business analyst editano il workflow accanto agli ingegneri. Camunda 8 (la riscrittura cloud-native) ha sostituito il vecchio Camunda 7 (basato su Activiti) a partire dal 2022 circa.
Argo Workflows è l’opzione Kubernetes-native. I workflow si definiscono come custom resource di Kubernetes, ogni step è un pod, e l’orchestrator è esso stesso un controller Kubernetes. Si adatta a pipeline di dati e ML che già girano su Kubernetes; l’inquadramento della lezione 57 ha coperto Argo dall’angolo dell’orchestrazione dati.
La scelta di solito viene fuori dall’infrastruttura esistente del team. Casa pesantemente AWS con ingegneri a loro agio in JSON: Step Functions. Team greenfield che vuole che la logica del workflow sia codice ordinario: Temporal. Enterprise con tradizioni BPMN: Camunda. Team dati Kubernetes-native: Argo.
Riferimenti incrociati e il prossimo passo
Il pattern si collega a diverse lezioni precedenti. La lezione 15 ha introdotto il 2PC e la saga come alternativa per la consistenza tra servizi. La lezione 16 ha posto le delivery semantics che l’event bus deve onorare. La lezione 42 è andata in profondità su Kafka, la spina dorsale più comune dei sistemi event-driven. La lezione 57 ha coperto gli orchestrator dall’angolo delle pipeline dati; questa lezione è lo stesso inquadramento applicato ai workflow di business cross-service.
La lezione 75 chiude l’apertura del modulo allargando ulteriormente lo zoom: una volta che i servizi e i loro workflow sono assestati, dove girano, geograficamente? I deployment multi-region ribaltano le stesse domande architetturali su un fianco, e i failure mode della replica cross-region sono la prossima cosa che il team deve mettersi a pensare.
Citazioni
- Documentazione Temporal,
https://docs.temporal.io/, consultato 2026-05-01. Il workflow engine code-driven dominante. - Documentazione AWS Step Functions,
https://docs.aws.amazon.com/step-functions/, consultato 2026-05-01. - Documentazione Camunda 8,
https://docs.camunda.io/, consultato 2026-05-01. - Documentazione Argo Workflows,
https://argo-workflows.readthedocs.io/, consultato 2026-05-01. - Chris Richardson, “Microservices Patterns”, Manning, 2018. Riferimento standard per il pattern saga, choreography vs orchestration, e il design event-driven.
- Sam Newman, “Building Microservices, 2nd Edition”, O’Reilly, 2021. Capitolo sulla comunicazione tra servizi.
- Hector Garcia-Molina e Kenneth Salem, “Sagas”, ACM SIGMOD 1987. Il paper originale sulla saga.