Pattern EDA: coreografia, orchestrazione e tipi di eventi 💃🕺
L’EDA non è “un pattern”. È una famiglia di pattern. E come tutte le famiglie, può essere adorabile o drammatica, dipende da quanto la governi.
Coreografia vs orchestrazione (cosa stai davvero scegliendo) 🎭
Coreografia 🧩
flowchart LR
A[Order Service] -->|OrderCreated| EB[(Event broker)]
EB --> P[Payment Service]
EB --> I[Inventory Service]
P -->|PaymentAuthorized| EB
I -->|InventoryReserved| EB
Ogni servizio reagisce agli eventi che gli interessano e pubblica nuovi eventi.
- Vantaggi: decentralizzata, scalabile, aggiungi consumer senza toccare il producer.
- Svantaggi: il flusso di business può diventare difficile da “vedere” (e da debuggare) senza strumenti.
Quando funziona bene:
- side effects indipendenti;
- workflow “a cascata” semplice;
- team maturi su osservabilità.
Orchestrazione 🧭
flowchart LR
O[Orchestratore / Workflow] -->|comanda step| S1[Servizio A]
O -->|comanda step| S2[Servizio B]
S1 -->|evento di esito| EB2[(Event broker)]
S2 -->|evento di esito| EB2
EB2 --> O
Un componente (workflow/orchestrator) dirige i passi e decide il prossimo step.
- Vantaggi: visibilità del processo, controllo, gestione errori più centrale.
- Svantaggi: rischi un nuovo monolite (solo più elegante), coupling al workflow.
Quando funziona bene:
- processi multi-step con vincoli forti;
- necessità di controllare timeout, retry, compensazioni in modo coerente.
Event Notification 📨
La variante più minimale di payload: l’evento annuncia che qualcosa è successo, ma porta pochissimi dati — di solito solo un ID o un riferimento al soggetto coinvolto. Il consumer che riceve la notifica, se ha bisogno di dettagli, fa una chiamata diretta al producer per recuperare lo stato aggiornato.
sequenceDiagram
Producer->>Broker: OrderCreated { orderId: 42 }
Broker->>Consumer: OrderCreated { orderId: 42 }
Consumer->>Producer: GET /orders/42
Producer-->>Consumer: { stato completo }
- Vantaggi: contratti piccoli e stabili, cambio di stato interno senza rinegoziare il contratto dell’evento, rischio minore di esporre dati sensibili nel canale asincrono.
- Svantaggi: reintroduce coupling sincrono in forma camuffata — il consumer deve chiamare il producer dopo aver ricevuto l’evento. Se il producer è irraggiungibile in quel momento, il processing fallisce o richiede logica di retry separata. In scenari ad alto volume può generare un “thundering herd” di callback simultanee.
Quando funziona bene:
- dati sensibili che non vuoi replicare nel broker (GDPR, informazioni riservate);
- stato che cambia spesso ma con pochi consumer realmente interessati ai dettagli;
- vuoi mantenere il contratto dell’evento minimo e disaccoppiato dall’implementazione interna.
La trattazione completa — criteri di scelta, confronto con ECST e trade-off su coupling sincrono — è nella guida su event design.
Event-Carried State Transfer (ECST) 📦
L’alternativa alla notifica: metti nello stesso evento tutto (o quasi) lo stato rilevante, così il consumer riceve già quello che gli serve per elaborare l’evento senza dover richiamare nessuno.
sequenceDiagram
Producer->>Broker: OrderCreated { orderId: 42, userId: 7, items: [...], totale: 89.90, ... }
Broker->>Consumer: OrderCreated { orderId: 42, userId: 7, items: [...], totale: 89.90, ... }
Note over Consumer: elabora senza callback
- Vantaggi: il consumer è autonomo a runtime — può processare l’evento anche se il producer è temporaneamente irraggiungibile, zero roundtrip aggiuntivi, latenza più prevedibile. È il pattern naturale per costruire read model denormalizzati (tipico in CQRS) o per scenari ad alto throughput dove ogni roundtrip in più è costoso.
- Svantaggi: payload più grandi (impatto su bandwidth, storage nel broker, costi di rete), rischio di esporre nello stream dati sensibili che andrebbero protetti, e — il punto più sottile — schema evolution diventa più complessa: ogni campo nel payload diventa parte del contratto pubblico, e rimuovere o rinominare qualcosa può rompere consumer che nemmeno ti aspetti.
C’è anche il problema dell’ordine: se arrivano eventi fuori sequenza e il consumer aggiorna il proprio stato locale, può trovarsi con dati stantii anche se tecnicamente “più recenti” secondo il timestamp locale.
Quando funziona bene:
- consumer che devono essere altamente autonomi o operare in modalità offline-first;
- scenari ad alto throughput dove il costo del callback è significativo;
- costruzione di read model specializzati (projection/materialized view in stile CQRS).
Per la trattazione completa con criteri di scelta, confronto con Event Notification e strategie su schema evolution: vedi la guida su event design.
Event Sourcing e CQRS 🏛️
Nei sistemi EDA maturi, prima o poi arriva la domanda: “ci basta reagire agli eventi, o dobbiamo anche ricostruire stato e ottimizzare la lettura?”. Qui entrano in gioco Event Sourcing (la storia degli eventi come fonte di verità immutabile) e CQRS (modello di scrittura e modello di lettura separati).
Non sono obbligatori: servono quando audit, replay e read model specializzati sono requisiti reali, non desideri da whiteboard. Per una trattazione completa — snapshot, schema evolution e criteri di adozione — vedi la guida dedicata su Event Sourcing e CQRS.
Cosa decidere davvero (senza perdersi in teoria) 🧰
Questa guida non serve a collezionare pattern come figurine: serve a scegliere. Quando stai disegnando un flusso event-driven, le decisioni che contano davvero sono poche e abbastanza “brutali”.
- Coreografia: se il flusso è lineare e i side effects sono indipendenti, di solito basta. Esempio:
OrdineCreato→ riserva magazzino → conferma; eviti un singolo punto centrale, ma devi accettare che coordinamento e timeout siano “distribuiti”. - Orchestrazione: se ti serve visibilità e controllo centralizzato del processo (stato, timeout, compensazioni coerenti), ha senso. Utile anche per politiche centralizzate (retry, circuit breaker) su processi multi-step, ma ti porti a casa più coupling e più manutenzione sui workflow.
- Notification: se vuoi contratti più stabili e piccoli, riduce il payload. Ma spesso reintroduce coupling sincrono: i consumer devono fare fetch dal producer e l’asincronia diventa una dipendenza “a latenza variabile”.
- ECST: se vuoi consumer molto autonomi, riduce callback/roundtrip. In cambio aumentano responsabilità e rischi su payload (dimensione/bandwidth), privacy e schema evolution/versioning.
- Event Sourcing / CQRS: non sono decisioni da prendere al primo whiteboard. Diventano rilevanti quando hai requisiti concreti di audit, replay o read model molto specializzati. Se non sai ancora se ne hai bisogno, probabilmente non ne hai bisogno. Se e quando lo capisci, vedi la guida dedicata.
Nota pratica: qualunque scelta fai, preparati a misurarla. Senza osservabilità (correlation ID, tracing/logging decenti) “coreografia vs orchestrazione” diventa una discussione filosofica… e di solito vince il caos.
Prossimi passi 🚀
Ora che hai scelto come disegnare i flussi (coreografia/orchestrazione) e che stile di payload usare, il resto è disciplina: contratti e operatività.
- Per capire queue/stream/pub-sub: vedi la guida di messaggistica.
- Per contratti e schema evolution: vedi la guida su event design.
- Per idempotenza, retry e DLQ: vedi la guida su operatività.