Event Sourcing
La verità è la sequenza append-only di eventi; lo stato è una riduzione (o snapshot + replay). Audit e temporal queries diventano naturali.
Approfondimento
Definizioni, contesto e collegamenti tra concetti — leggi prima del palcoscenico interattivo se parti da zero.
Stream append-only e proiezione
Event Sourcing memorizza la storia come sequenza immutabile di eventi di dominio. Lo “stato corrente” è una riduzione (fold) degli eventi, eventualmente accelerata da snapshot che catturano un checkpoint.
Ottiene audit naturale, temporal queries e replay per test di regressione sul dominio. Paghi in complessità: migrazioni degli eventi storici, gestione degli schemi, tool di ispezione e training del team.
Quando evitarlo
Se ti basta una tabella con CRUD e un audit log append-only per compliance, spesso spendi meno e vai più veloce. ES ha senso quando il dominio è event-first, servono ricostruzioni storiche affidabili o integrazioni multiple sulla stessa timeline.
Gli snapshot riducono il replay lungo; le funzioni di proiezione vanno versionate con cura quando cambi la semantica di eventi legacy, altrimenti ricostruisci stati sbagliati.
Concurrency control sullo stream
L’append usa spesso `expectedVersion` (o equivalente) per evitare write concorrenti sullo stesso aggregato: se la versione non combacia, rifiuti e chiedi al chiamante di ricaricare e riprovare — è lo stesso spirito dell’optimistic locking sulle righe SQL.
In lettura, separa stream tecnico da eventi di dominio esposti: gli strumenti admin possono mostrare la timeline grezza, mentre i servizi applicano solo ciò che il contratto consente.
Palcoscenico
Append-only stream (semplificato)
- + FundsReserved
Contratto e codice
Schema JSON basato su CloudEvents 1.0, poi snippet server e client.
{
"specversion": "1.0",
"source": "/orders-service",
"id": "550e8400-e29b-41d4-a716-446655440000",
"time": "2026-05-13T12:00:00.000Z",
"datacontenttype": "application/json",
"type": "com.example.ledger.append",
"data": {
"streamId": "acc_7712",
"expectedVersion": 12,
"events": [
{ "type": "FundsReserved", "payload": { "amount": 5000 } },
{ "type": "FundsCaptured", "payload": { "amount": 5000 } }
]
}
}