Vai al contenuto
Event Sourcing e CQRS

Event Sourcing e CQRS: quando la storia conta 🏛️

Event Sourcing e CQRS sono tra i pattern più citati — e più fraintesi — nell’ecosistema EDA. Di solito appaiono insieme, come se fossero un pacchetto unico. Non lo sono: si combinano bene, ma hanno motivazioni diverse e costi diversi.

Event Sourcing: il log è la fonte di verità 📜

    flowchart LR
  CMD[Command] --> AGG[Aggregate]
  AGG -->|emette| E1[Evento A v1]
  AGG -->|emette| E2[Evento B v1]
  E1 --> STORE[(Event Store)]
  E2 --> STORE
  STORE --> PROJ["Proiezione<br/>(stato corrente)"]
  STORE --> AUDIT[Audit log]
  

In un sistema tradizionale, salvi lo stato corrente dell’entità. Con Event Sourcing, salvi la sequenza di eventi che ha prodotto quello stato. Lo stato corrente è una proiezione della storia.

Esempio: OrdineCreatoArticoloAggiuntoPagamentoAutorizzatoOrdineSpedito. Lo stato dell’ordine si ricostruisce applicando gli eventi in sequenza.

Vantaggi concreti ✅

  • Audit trail nativo: la storia è il dato — non servono tabelle di log aggiuntive.
  • Temporal queries: ricostruisci lo stato in qualsiasi punto nel tempo.
  • Replay e riproiezioni: se cambi la logica business, puoi ricostruire le read view dai dati originali.
  • Debugging: sai esattamente cosa è successo, in che ordine e perché — senza dover inferire da uno snapshot.

Costi reali (quelli che non vedi nelle slide) 💸

  • Event store: hai bisogno di uno storage ottimizzato per append (Kafka, EventStoreDB, o Postgres con table append-only dedicata).
  • Snapshoting: senza snapshot, ricostruire lo stato di un’entità con 10.000 eventi è lento.
  • Schema evolution: gli eventi sono immutabili, ma il tuo dominio non lo è. Versioning e trasformazioni sono obbligatori, non opzionali.
  • Curva di apprendimento: il team deve cambiare modo di pensare. Non tutti trovano naturale “ragionare a eventi” invece di “CRUD su tabelle”.

Snapshoting: non ricominciare da zero ogni volta 📸

Quando la storia di un aggregato diventa lunga, riapplicare tutti gli eventi ad ogni accesso diventa costoso. La soluzione è il snapshot: una fotografia dello stato dell’aggregato salvata a intervalli regolari. Alla successiva lettura, si parte dall’ultimo snapshot e si applicano solo gli eventi successivi.

    flowchart LR
  SNAP["Snapshot<br/>(stato a T=1000)"] --> E1001[Evento 1001]
  E1001 --> E1002[Evento 1002]
  E1002 --> STATO[Stato corrente]
  

Strategia pratica: crea snapshot ogni N eventi o dopo un certo intervallo di tempo. Lo snapshot non sostituisce gli eventi originali — è un’ottimizzazione sulla lettura, non una cancellazione della storia.

CQRS: quando leggere e scrivere hanno bisogno di modelli diversi ⚖️

    flowchart TD
  C["Command<br/>(write side)"] --> AGG["Aggregate / Write Model"]
  AGG --> ES[Event Store]
  ES --> PROJ1["Proiezione A<br/>read model 1"]
  ES --> PROJ2["Proiezione B<br/>read model 2"]
  Q1[Query 1] --> PROJ1
  Q2[Query 2] --> PROJ2
  

CQRS (Command Query Responsibility Segregation) separa il modello di scrittura (comandi → eventi) dal modello di lettura (proiezioni ottimizzate per le query).

  • Il write model è normalizzato, coerente, orientato agli invarianti di business.
  • I read model sono denormalizzati, ottimizzati per i pattern di query specifici.

Perché non usare un solo modello? 🧠

In sistemi semplici, è giusto usare un solo modello. CQRS ha senso quando:

  • le query di lettura richiedono strutture radicalmente diverse da quelle di scrittura;
  • la frequenza di lettura è molto più alta delle scritture e vuoi scalarle separatamente;
  • hai bisogno di più “viste” degli stessi dati (dashboard, ricerca, notifiche) con esigenze diverse.

Consistenza eventuale nelle proiezioni 🕐

Le proiezioni vengono aggiornate asincronamente dopo la pubblicazione degli eventi. C’è sempre un lag tra la scrittura e la lettura aggiornata.

Conseguenze pratiche:

  • l’interfaccia deve comunicare correttamente la consistenza eventuale (“operazione in corso”);
  • pianificare il reprocessing delle proiezioni con idempotenza garantita;
  • scegliere esplicitamente dove calcolare le denormalizzazioni (lato producer evento o lato consumer/projection).

Event Sourcing + CQRS: combo o alternative? 🤔

Spesso si vedono insieme, ma sono indipendenti:

Event Sourcing Senza Event Sourcing
Con CQRS Combo classica: eventi come write side, proiezioni come read model Possibile: write su RDBMS, read model separati
Senza CQRS Raro ma possibile: ES per audit/replay, stato letto dall’event store Sistema tradizionale

La combo ES + CQRS funziona molto bene quando:

  • hai bisogno di audit/replay (motivazione ES);
  • hai pattern di lettura diversi dalla struttura degli eventi (motivazione CQRS);
  • il dominio è genuinamente event-centric e non CRUD mascherato.

Schema evolution sugli eventi immutabili 🧬

In Event Sourcing, gli eventi sono immutabili: non puoi modificare un evento già scritto. Questo rende la schema evolution più delicata rispetto a un sistema CRUD.

Strategie pratiche:

  • Upcasting: quando leggi un vecchio evento, lo trasformi nel formato nuovo prima di processarlo. Implementato nel layer di lettura, non nello storage.
  • Versioning esplicito: OrdineCreato_v1, OrdineCreato_v2 come tipi distinti, con handler separati per ogni versione.
  • Weak schema: usa un formato flessibile (JSON) e scrivi il layer di lettura in modo difensivo verso i campi opzionali.

Non esiste la soluzione “gratis”: scegli in base alla frequenza attesa di cambiamenti del dominio e al numero di consumer che leggono l’event store.

Quando NON usare ES/CQRS 🛑

Questi pattern non sono adatti a tutti i sistemi. Evitali se:

  • il dominio è essenzialmente CRUD senza requisiti di audit o business logic complessa;
  • il team non è pronto per la complessità operativa (event store, snapshot, reprocessing delle proiezioni);
  • non hai requisiti di temporal queries, rebuild da zero o audit normativo;
  • il sistema non raggiunge una scala in cui separare read e write vale il costo di ownership.

Prima di adottare ES+CQRS, chiediti: “Avrei bisogno di ricostruire lo stato da zero in futuro?” Se la risposta onesta è no, probabilmente un event log su Kafka + un buon read model è tutto ciò che serve.

Checklist prima di adottare ES/CQRS ✅

  • Hai identificato requisiti reali di audit, replay o temporal queries?
  • Hai scelto l’event store (Kafka, EventStoreDB, Postgres append-only)?
  • Hai una strategia di snapshoting per aggregati a lunga vita?
  • Il team ha compreso il cambio di paradigma rispetto al CRUD classico?
  • Hai pianificato schema evolution e upcasting per gli eventi futuri?
  • Hai definito il reprocessing delle proiezioni con idempotenza verificata?
  • Hai misurato (o stimato) il lag delle proiezioni e comunicato le aspettative a prodotto e UX?

Prossimi passi 🚀

Ultimo aggiornamento il