Skip to content
Luca Zanchi / Tech Blog

Event-driven: quando è davvero la scelta giusta?

Le architetture event-driven vengono spesso presentate come la naturale evoluzione delle integrazioni tradizionali.

Un sistema pubblica un evento, gli altri lo ricevono e reagiscono in modo indipendente.

Il produttore non deve conoscere tutti i destinatari. I sistemi possono elaborare le informazioni con tempi differenti. Un nuovo consumer può essere aggiunto senza modificare necessariamente chi ha generato l’evento.

Il modello appare elegante:

Sistema sorgente
↓
Evento
↓
Message broker
↙ ↓ ↘
CRM ERP Analytics

In alcuni contesti, questa architettura produce un valore concreto.

Permette di assorbire picchi, isolare temporaneamente sistemi non disponibili, distribuire le elaborazioni e ridurre alcune forme di accoppiamento.

In altri casi, però, trasforma un flusso relativamente semplice in un sistema distribuito più difficile da comprendere, verificare e gestire.

La domanda utile non è quindi:

Dovremmo adottare un’architettura event-driven?

È piuttosto:

Quale problema stiamo cercando di risolvere e perché una comunicazione asincrona dovrebbe essere più adatta?

Un’architettura event-driven non elimina la complessità.

La sposta.

Riduce alcune dipendenze dirette, ma introduce messaggi, code, duplicazioni, ritardi, consistenza eventuale, gestione dello stato e nuove esigenze di osservabilità.

La scelta è corretta quando questo scambio produce un vantaggio reale.

1. Che cosa significa davvero event-driven

In un’architettura event-driven, un componente comunica che qualcosa è accaduto.

Per esempio:

L’evento viene pubblicato e uno o più componenti possono reagire.

Order Created
↓
┌────┼───────────┐
↓ ↓ ↓
ERP Email Preparazione spedizione

Il produttore dell’evento non dovrebbe coordinare direttamente tutte le operazioni successive.

Comunica un fatto rilevante. Sono i consumer a decidere se e come utilizzarlo.

Questo modello è differente da una chiamata sincrona tradizionale:

E-commerce → crea ordine nell’ERP → attende la risposta

Nel modello sincrono, chi effettua la chiamata conosce il destinatario e attende un risultato.

Nel modello event-driven, il produttore pubblica un’informazione e può continuare senza attendere che ogni consumer completi il proprio lavoro.

La differenza non riguarda soltanto la tecnologia.

Cambia il modo in cui vengono distribuite responsabilità, errori e stato del processo.

2. Eventi e comandi non sono la stessa cosa

Una distinzione importante è quella tra evento e comando.

Un comando esprime una richiesta:

CreateInvoice
SendNotification
UpdateInventory

Un evento descrive qualcosa che è già avvenuto:

OrderConfirmed
InvoiceCreated
InventoryUpdated

La differenza può sembrare terminologica, ma ha conseguenze architetturali.

2.1 Il comando ha un destinatario

Un comando chiede a un componente specifico di eseguire un’azione.

Chi lo invia conosce almeno la capacità che vuole utilizzare.

Order Service → CreateInvoice → Billing Service

Il servizio di fatturazione può accettare o rifiutare la richiesta.

2.2 L’evento comunica un fatto

Un evento non dovrebbe ordinare ai consumer cosa fare.

Comunica che lo stato del dominio è cambiato.

Order Service → OrderConfirmed

Il servizio di fatturazione può utilizzare l’evento per creare una fattura. Un servizio di analytics può registrarlo. Un sistema di notifiche può inviare un messaggio.

Il produttore non deve necessariamente conoscere queste reazioni.

2.3 Confondere i due modelli crea ambiguità

Un messaggio chiamato OrderCreated potrebbe sembrare un evento, ma essere in realtà utilizzato come richiesta implicita al sistema di logistica.

In questo caso il produttore si aspetta un comportamento specifico, anche se il contratto non lo dichiara chiaramente.

Questa ambiguità rende difficile comprendere:

Nominare correttamente i messaggi aiuta a rendere esplicite le responsabilità.

3. Quale problema risolve l’asincronia

L’asincronia non dovrebbe essere introdotta soltanto perché i sistemi sono numerosi o perché un message broker è già disponibile.

Deve risolvere un’esigenza concreta.

3.1 Ridurre l’accoppiamento temporale

In una chiamata sincrona, entrambi i sistemi devono essere disponibili nello stesso momento.

Sistema A → Sistema B

Se il sistema B non risponde, anche il processo del sistema A può bloccarsi.

Con una coda, il messaggio può essere conservato finché il destinatario non torna disponibile.

Sistema A → Coda → Sistema B

Questo è utile quando il produttore può completare la propria attività senza attendere l’elaborazione immediata del consumer.

3.2 Assorbire picchi di volume

Un sistema può produrre richieste più velocemente di quanto il destinatario riesca a elaborarle.

La coda funziona come un buffer.

Il produttore continua a pubblicare, mentre i consumer elaborano i messaggi secondo la capacità disponibile.

Questo permette di gestire variazioni temporanee del carico senza dimensionare ogni componente sul picco massimo.

3.3 Distribuire lo stesso fatto a più consumer

Quando più sistemi devono reagire allo stesso cambiamento, un evento può evitare che il produttore li chiami direttamente uno per uno.

ProductUpdated
↓
┌────┼───────────┐
↓ ↓ ↓
Search Cache Marketplace

Il produttore rimane meno dipendente dal numero e dall’identità dei destinatari.

3.4 Eseguire attività non immediate

Alcune elaborazioni non devono essere completate durante la richiesta dell’utente.

Per esempio:

Spostare queste attività in processi asincroni può ridurre il tempo di risposta percepito e isolare i fallimenti secondari.

3.5 Evolvere i consumer in modo indipendente

Se il contratto degli eventi rimane stabile, diversi consumer possono essere sviluppati e distribuiti separatamente.

Questo vantaggio diventa rilevante quando più team gestiscono componenti differenti e hanno realmente bisogno di cicli di rilascio autonomi.

4. Quando una chiamata sincrona è più semplice e corretta

L’event-driven non è una soluzione superiore in ogni situazione.

Molti processi sono più comprensibili se rappresentati attraverso una chiamata diretta.

4.1 Il risultato serve immediatamente

Se un utente deve sapere subito se un pagamento è stato autorizzato, una risposta asincrona può rendere il flusso più complesso.

La richiesta ha bisogno di un risultato chiaro:

Richiesta → Elaborazione → Risposta

Trasformarla in:

Richiesta → Messaggio → Attesa → Evento di risposta

introduce stato, timeout e correlazione senza necessariamente produrre un vantaggio.

4.2 Esiste un unico destinatario stabile

Se un servizio deve interrogare un altro servizio specifico e la relazione è chiara, una chiamata API può essere sufficiente.

Aggiungere un broker tra due componenti non li rende automaticamente meno accoppiati.

Il produttore continua spesso a dipendere:

L’accoppiamento può diventare meno visibile, ma non necessariamente scomparire.

4.3 Il processo è breve e lineare

Un flusso composto da pochi passaggi, con errori facilmente gestibili, può essere più semplice da seguire in modo sincrono.

La sequenza rimane esplicita nel codice e l’errore può essere restituito direttamente al chiamante.

4.4 Il volume è limitato

Quando il carico è prevedibile e contenuto, il buffering offerto da una coda può non essere necessario.

Il costo operativo del broker, del monitoraggio e della gestione dei consumer potrebbe superare il beneficio.

4.5 Il team non dispone ancora delle capacità operative necessarie

Un sistema distribuito richiede strumenti e competenze per osservare code, messaggi, retry, consumer e ritardi.

Adottare il modello prima di avere queste capacità può diminuire l’affidabilità anziché migliorarla.

5. Il disaccoppiamento non è assoluto

Uno dei principali argomenti a favore delle architetture event-driven è il disaccoppiamento.

È un beneficio reale, ma deve essere interpretato con precisione.

Il produttore può non conoscere l’indirizzo o l’implementazione dei consumer.

Rimane però legato a un contratto informativo.

Un evento come:

{
"eventType": "OrderConfirmed",
"orderId": "ORD-1824",
"customerId": "CUS-77",
"total": 129.90,
"currency": "EUR"
}

crea comunque dipendenze.

I consumer dipendono:

Il produttore non è libero di modificare arbitrariamente il contratto.

Il disaccoppiamento riguarda quindi alcune dimensioni:

Non elimina l’accoppiamento semantico.

Se il significato dell’evento non è stabile e condiviso, i consumer possono interpretarlo in modi differenti.

6. La complessità introdotta

Un sistema event-driven aggiunge componenti e comportamenti che devono essere gestiti.

Il messaggio non passa semplicemente da A a B.

Può essere memorizzato, duplicato, ritardato, elaborato fuori ordine o spostato in una coda degli errori.

6.1 Consegna almeno una volta

Molte infrastrutture garantiscono che un messaggio venga consegnato almeno una volta.

Questo significa che può essere consegnato più volte.

Il consumer deve quindi essere preparato a ricevere duplicati.

Evento 1 → Consumer
Evento 1 → Consumer

Senza idempotenza, la seconda elaborazione può:

La duplicazione non è un caso eccezionale. Deve essere considerata parte del modello.

6.2 Ordine dei messaggi

Non sempre gli eventi vengono elaborati nello stesso ordine in cui sono stati prodotti.

Un consumer potrebbe ricevere:

CustomerAddressUpdated

prima di:

CustomerCreated

Oppure due aggiornamenti dello stesso elemento potrebbero essere elaborati da consumer differenti in ordine diverso.

Quando l’ordine è importante, è necessario definire:

6.3 Consistenza eventuale

Dopo la pubblicazione di un evento, i sistemi non si aggiornano necessariamente nello stesso momento.

Per un certo intervallo possono mostrare stati differenti.

ERP: ordine confermato
CRM: aggiornamento in attesa
Analytics: dato non ancora disponibile

Questa consistenza eventuale può essere accettabile, ma deve essere compresa dal business e dalle interfacce utente.

Se l’organizzazione si aspetta che tutti i sistemi riflettano immediatamente lo stesso stato, l’asincronia può generare confusione.

6.4 Errori distribuiti

In un flusso sincrono, il chiamante riceve spesso un errore immediato.

In un sistema event-driven, il produttore può aver completato correttamente il proprio lavoro mentre uno dei consumer fallisce alcuni minuti dopo.

La gestione dell’errore diventa una responsabilità separata:

6.5 Difficoltà di ricostruzione

Un processo può attraversare più code e consumer.

OrderConfirmed
↓
Billing
↓
InvoiceCreated
↓
DocumentStorage
↓
InvoiceArchived

Quando il risultato finale manca, non è sufficiente controllare un solo servizio.

Serve ricostruire l’intera catena.

Senza identificativi di correlazione e osservabilità distribuita, l’analisi può diventare molto costosa.

7. Event notification ed event-carried state transfer

Non tutti gli eventi devono contenere la stessa quantità di informazioni.

Esistono due approcci comuni.

7.1 Notifica dell’evento

Il messaggio comunica che qualcosa è accaduto e contiene pochi riferimenti.

{
"eventType": "CustomerUpdated",
"customerId": "CUS-77"
}

Il consumer deve interrogare il sistema sorgente per ottenere i dati aggiornati.

Questo approccio mantiene il messaggio piccolo e riduce la duplicazione dei dati.

Introduce però una dipendenza operativa: il consumer deve poter contattare il sistema sorgente.

Se molti consumer reagiscono contemporaneamente, il produttore può ricevere numerose richieste.

7.2 Trasferimento dello stato nell’evento

Il messaggio contiene i dati necessari al consumer.

{
"eventType": "CustomerUpdated",
"customerId": "CUS-77",
"email": "customer@example.com",
"status": "active",
"updatedAt": "2026-07-30T12:40:00Z"
}

Il consumer può elaborare il messaggio senza interrogare il produttore.

Questo aumenta l’autonomia e la resilienza.

Produce però altre responsabilità:

Non esiste una soluzione sempre corretta.

La scelta dipende da quanta autonomia serve ai consumer e da quali dati sia opportuno distribuire.

8. Progettare buoni eventi

Un evento non dovrebbe essere semplicemente la serializzazione di una tabella o dell’oggetto interno di un’applicazione.

Dovrebbe rappresentare un fatto significativo per il dominio.

8.1 Nomi al passato

Un evento descrive qualcosa che è avvenuto.

Nomi come:

sono generalmente più chiari di nomi generici come:

Il nome dovrebbe comunicare il significato senza richiedere l’analisi completa del payload.

8.2 Momento di emissione definito

Deve essere chiaro quando l’evento viene pubblicato.

OrderCreated può significare:

Interpretazioni diverse producono comportamenti differenti nei consumer.

8.3 Identità dell’entità

Il messaggio deve contenere un identificativo stabile e comprensibile.

Quando più sistemi utilizzano identificativi differenti, deve essere chiaro quale venga pubblicato e come possa essere correlato agli altri.

8.4 Tempo e versione

Sono spesso utili:

Questi metadata aiutano a gestire duplicazioni, ordinamento e ricostruzione del flusso.

8.5 Minimo contenuto necessario

Il payload dovrebbe fornire le informazioni necessarie senza trasformarsi in una copia completa dello stato interno.

Pubblicare ogni campo “nel caso possa servire” aumenta il rischio di creare contratti difficili da modificare.

9. Idempotenza come requisito di base

In un sistema event-driven è prudente assumere che ogni messaggio possa arrivare più volte.

L’idempotenza permette di elaborare lo stesso evento senza produrre effetti duplicati.

Un consumer può registrare gli identificativi già elaborati:

Event ID: EVT-9348
Stato: completato

Se lo stesso evento viene ricevuto nuovamente, il consumer può ignorarlo oppure restituire l’esito precedente.

Altre strategie includono:

La strategia dipende dal tipo di effetto.

Aggiornare lo stato di un cliente può essere relativamente semplice da rendere idempotente.

Inviare una comunicazione o avviare un pagamento richiede controlli più espliciti.

L’idempotenza dovrebbe essere progettata insieme al consumer, non aggiunta soltanto dopo il primo problema di duplicazione.

10. Retry e dead-letter queue

Quando un consumer fallisce, un nuovo tentativo può essere appropriato.

Non tutti gli errori, però, sono temporanei.

10.1 Errori temporanei

Possono includere:

Questi errori possono essere ritentati con intervalli crescenti.

Tentativo 1 → 10 secondi
Tentativo 2 → 1 minuto
Tentativo 3 → 5 minuti

10.2 Errori permanenti

Possono includere:

Ripetere automaticamente lo stesso messaggio non modifica la condizione.

Dopo un numero definito di tentativi, il messaggio può essere spostato in una dead-letter queue o in uno stato di errore.

Questo non risolve il problema.

Lo isola per evitare che blocchi l’intero flusso.

Devono quindi esistere procedure per:

Una dead-letter queue senza un processo operativo diventa semplicemente un archivio di problemi dimenticati.

11. Il problema delle transazioni distribuite

In un processo distribuito, un’operazione può riuscire in un sistema e fallire in un altro.

Supponiamo che la conferma di un ordine attivi:

  1. prenotazione del magazzino;
  2. acquisizione del pagamento;
  3. creazione della spedizione.

Il magazzino può completare la prenotazione, mentre il pagamento fallisce.

Non esiste necessariamente una transazione unica capace di annullare automaticamente tutte le operazioni.

Serve quindi definire cosa debba accadere.

Possibili strategie includono:

11.1 Orchestrazione

Un componente centrale decide quali passaggi eseguire e come reagire agli esiti.

Order Process
├─ Prenota magazzino
├─ Acquisisce pagamento
└─ Crea spedizione

Il processo è più semplice da ricostruire, ma il coordinatore assume una responsabilità importante.

11.2 Coreografia

Ogni servizio reagisce agli eventi e pubblica nuovi eventi.

OrderConfirmed
↓
StockReserved
↓
PaymentCaptured
↓
ShipmentCreated

La dipendenza diretta tra servizi diminuisce, ma il processo complessivo può diventare difficile da vedere.

La coreografia funziona meglio quando le reazioni sono relativamente indipendenti.

Quando esiste una sequenza di business rigida, con compensazioni e stato complessivo, l’orchestrazione può essere più comprensibile.

12. Osservabilità in un sistema event-driven

In un’architettura asincrona, sapere che il produttore ha pubblicato correttamente un evento non dimostra che il processo sia completato.

Serve osservare l’intero percorso.

Per ogni messaggio dovrebbe essere possibile conoscere:

12.1 Identificativi di correlazione

Un identificativo comune permette di collegare eventi differenti appartenenti allo stesso processo.

Correlation ID: COR-2817
OrderConfirmed
↓
StockReserved
↓
PaymentCaptured
↓
ShipmentRequested

Senza questa correlazione, ogni servizio mostra soltanto una parte del flusso.

12.2 Metriche operative

Le metriche utili possono includere:

Non basta monitorare che il broker sia disponibile.

Il broker può funzionare correttamente mentre i processi di business rimangono bloccati.

12.3 Visibilità per elemento

Le metriche aggregate mostrano la salute generale.

Durante un incidente serve però trovare il singolo ordine, cliente o documento.

Una buona osservabilità deve supportare entrambi i livelli:

13. Evoluzione e versionamento degli eventi

Gli eventi diventano contratti condivisi da più consumer.

Modificarli richiede quindi attenzione.

13.1 Cambiamenti compatibili

Aggiungere un campo facoltativo può essere compatibile se i consumer ignorano ciò che non conoscono.

Anche in questo caso è necessario verificare:

13.2 Cambiamenti incompatibili

Rinominare un campo, modificarne il tipo o cambiarne il significato può interrompere i consumer esistenti.

Possibili strategie includono:

13.3 Schema registry e validazione

In sistemi con molti eventi, un registro degli schemi può aiutare a:

La tecnologia non sostituisce però la governance.

Serve comunque sapere chi può modificare un contratto e chi deve essere coinvolto.

14. Proprietà e responsabilità

Un evento condiviso crea una dipendenza organizzativa oltre che tecnica.

Per ogni evento dovrebbe essere chiaro:

Anche la responsabilità dei consumer deve essere esplicita.

Chi interviene quando un consumer accumula ritardo? Chi gestisce la dead-letter queue? Chi decide se un evento possa essere rielaborato?

Senza queste risposte, l’infrastruttura può essere tecnicamente distribuita mentre la responsabilità rimane indefinita.

15. Event-driven non significa necessariamente microservizi

Un’architettura event-driven può esistere anche all’interno di un’applicazione modulare o tra pochi sistemi.

Allo stesso modo, un’architettura a microservizi può utilizzare prevalentemente comunicazioni sincrone.

I due concetti non sono equivalenti.

È possibile utilizzare eventi per:

La scelta dovrebbe essere guidata dal flusso e dalle esigenze operative, non dall’etichetta architetturale.

Un broker non trasforma automaticamente un sistema in una piattaforma moderna.

Può anche diventare un ulteriore componente inserito in un’architettura che non ne trae un beneficio concreto.

16. Segnali che l’event-driven può essere giustificato

Considero più ragionevole introdurre eventi quando sono presenti diverse di queste condizioni.

16.1 I produttori non devono attendere tutti i consumer

L’attività principale può essere completata anche se alcune elaborazioni secondarie avvengono successivamente.

16.2 Più sistemi reagiscono allo stesso fatto

Il produttore non dovrebbe coordinare direttamente un numero crescente di destinatari.

16.3 I picchi devono essere assorbiti

La velocità di produzione e quella di elaborazione possono differire significativamente.

16.4 Le indisponibilità temporanee sono frequenti

I sistemi destinatari possono essere isolati attraverso code e retry.

16.5 Le elaborazioni sono indipendenti

Il fallimento di un consumer non deve necessariamente bloccare gli altri.

16.6 I team hanno cicli realmente autonomi

I componenti devono poter evolvere senza distribuzioni coordinate continue.

16.7 Esiste la capacità di gestire operativamente il modello

Il team dispone di:

La presenza del problema tecnico senza la capacità operativa non è sufficiente.

17. Segnali che potrebbe essere prematuro

L’adozione può essere sproporzionata quando:

In questi casi, una chiamata API ben progettata può essere più affidabile e più facile da mantenere.

La semplicità non è una mancanza di ambizione architetturale.

È una proprietà da preservare finché il problema non richiede qualcosa di diverso.

18. Un percorso progressivo

Adottare un modello event-driven non richiede necessariamente una trasformazione completa.

È possibile iniziare da un confine specifico.

18.1 Separare un’attività secondaria

Un primo candidato può essere un’operazione che non deve bloccare la richiesta principale:

Questo permette di sperimentare il modello con un impatto limitato.

18.2 Definire un singolo evento

L’evento dovrebbe rappresentare un fatto chiaro, con un proprietario e un contratto documentato.

È preferibile evitare inizialmente eventi troppo generici come EntityChanged.

18.3 Costruire un consumer idempotente

Prima di aumentare il numero dei consumer, è utile verificare:

18.4 Introdurre visibilità operativa

Fin dalle prime implementazioni devono essere disponibili:

18.5 Valutare il valore prodotto

Dopo le prime adozioni è utile verificare se il modello ha realmente:

Se il vantaggio non è evidente, estendere l’architettura potrebbe soltanto amplificare il costo.

19. Un criterio pratico di decisione

Prima di introdurre un evento, considero utile rispondere a queste domande:

  1. Il produttore deve conoscere il destinatario?
  2. Ha bisogno di una risposta immediata?
  3. Il processo può accettare un ritardo?
  4. Il messaggio può essere elaborato più di una volta?
  5. L’ordine degli eventi è importante?
  6. Quale sistema possiede lo stato?
  7. Come verranno gestiti i fallimenti?
  8. Come verrà verificato il completamento?
  9. Chi monitora il consumer?
  10. Il beneficio supera il costo operativo?

Se le risposte rimangono ambigue, probabilmente non è ancora il momento di scegliere la tecnologia.

Il disegno del processo dovrebbe precedere la configurazione del broker.

20. Disaccoppiare con consapevolezza

Un’architettura event-driven può rendere un sistema più resiliente, estensibile e capace di gestire carichi variabili.

Può anche renderlo più difficile da comprendere.

Il valore non nasce dalla semplice presenza di eventi, topic o code.

Nasce quando l’asincronia risolve un vincolo reale:

In cambio, il sistema deve accettare e governare:

Per questo considero l’event-driven una scelta architetturale, non una preferenza tecnologica.

Disaccoppiare i sistemi non significa eliminare le relazioni tra loro.

Significa renderle meno dirette e, idealmente, più governabili.

Quando il problema giustifica questo scambio, gli eventi possono produrre un vantaggio significativo.

Quando non lo giustifica, una comunicazione diretta, osservabile e ben progettata rimane spesso la soluzione migliore.