← Torna agli articoli
Performance e database

Event Sourcing in pratica: non solo versioning dei dati, ma un potente strumento di analytics

Una guida pratica al pattern Event-Sourced Entity in Laravel: audit trail completi, ricostruzione dello stato a un momento specifico e perché i registri degli eventi sono una miniera d'oro per l'analytics di business.

Event Sourcing in pratica: non solo versioning dei dati, ma un potente strumento di analytics
Articolo in evidenza ↗

Event Sourcing in pratica: non solo versioning dei dati, ma un potente strumento di analytics

Oggi voglio discutere un pattern architetturale comune nelle applicazioni business complesse ma spesso sottovalutato: l'«Event-Sourced Entity» o, più semplicemente, un robusto sistema di versioning dei dati.

Cos'è?

Nel classico modello CRUD, aggiorniamo un record direttamente in una tabella come products: price = 1000. La cronologia delle modifiche viene silenziosamente persa.

Il modello event-driven funziona diversamente:

  1. L'entità principale memorizza solo lo stato attuale (ad esempio, l'ultimo prezzo, il livello delle scorte).
  2. Ogni cambiamento (un evento) viene registrato in una tabella di log separata come voce indipendente e immutabile.
  3. Lo stato attuale è il risultato dell'applicazione sequenziale di tutti gli eventi storici.

Un semplice esempio in Laravel

Consideriamo un'entità ProductPricing.

1. Migrazioni:

// Tabella principale (stato attuale) Schema::create('product_pricings', function (Blueprint $table) { $table->id(); $table->foreignId('product_id')->constrained(); $table->decimal('price', 10, 2); $table->integer('stock'); $table->timestamps(); }); // Tabella degli eventi (cronologia) Schema::create('pricing_events', function (Blueprint $table) { $table->id(); $table->foreignId('product_pricing_id')->constrained()->onDelete('cascade'); $table->string('type'); // 'price_changed', 'stock_incremented' $table->json('payload'); // { "old_price": 100, "new_price": 120, "reason": "supplier_cost" } $table->timestamp('performed_at'); $table->foreignId('user_id')->nullable(); // Chi lo ha avviato });

2. Modello ed evento:

// Event Data Transfer Object class PriceChangedEvent { public function __construct( public float $oldPrice, public float $newPrice, public string $changedBy, public ?string $reason = null ) {} } // Modello ProductPricing class ProductPricing extends Model { public function events() { return $this->hasMany(PricingEvent::class); } public function changePrice(float $newPrice, User $user, string $reason): void { // 1. Registrare l'evento $this->events()->create([ 'type' => 'price_changed', 'payload' => new PriceChangedEvent( oldPrice: $this->price, newPrice: $newPrice, changedBy: $user->email, reason: $reason ), 'performed_at' => now(), 'user_id' => $user->id, ]); // 2. Aggiornare lo stato attuale $this->update(['price' => $newPrice]); } }

Vantaggi di questo approccio:

✅ Audit trail completo e tracciabilità. Sappiamo non solo cosa è cambiato, ma anche quando, da chi e perché. Inestimabile per la conformità e la risoluzione delle controversie.

✅ Ricostruzione dello stato a un momento specifico. Possiamo "riprodurre" il modello fino a qualsiasi momento passato ricalcolando gli eventi fino a quel punto.

✅ Logica di business disaccoppiata. Gli eventi sono fatti puri. Possono essere elaborati in modo asincrono, inviati a code di messaggi (RabbitMQ, Kafka).

✅ Fondamento per CQRS. Il registro degli eventi è una perfetta fonte unica di verità per costruire vari modelli di lettura (ad esempio, specializzati per il reporting).

Svantaggi e complessità:

❌ Complessità aumentata. L'architettura diventa più complessa del "semplice CRUD". La coerenza tra la tabella principale e gli eventi deve essere gestita.

❌ Overhead delle prestazioni. Invece di un singolo aggiornamento, eseguiamo almeno due scritture. Eccessivo per sistemi ad alto throughput dove la cronologia non è necessaria.

❌ Interrogazione della cronologia. L'analisi dei dati richiede di lavorare con grandi flussi di eventi, il che può essere non banale.

Dove è particolarmente utile? Il caso d'uso chiave: l'analytics.

Questo approccio è una miniera d'oro per gli analisti di dati e la business intelligence. Perché?

  1. Analisi dei trend e delle dinamiche: Costruisci facilmente grafici dei cambiamenti di prezzo dei prodotti nel tempo, analizza la stagionalità e correla con le campagne marketing.
  2. Calcolo delle metriche: Prezzo medio su un periodo, tasso di rotazione delle scorte, frequenza delle modifiche.
  3. Analisi delle cause profonde: Raggruppare gli eventi per motivo o user_id rivela cosa guida più spesso i cambiamenti: fluttuazioni valutarie, azioni dei concorrenti, domanda stagionale.
  4. Modellazione "What-If": Sulla base di eventi storici, puoi costruire simulazioni. "Cosa succede al profitto se aumentiamo il prezzo del X% durante il periodo Y?"

Esempio di query SQL per l'analytics:

-- Ottenere la cronologia dei prezzi di un prodotto specifico nell'ultimo mese SELECT DATE(performed_at) as change_date, JSON_UNQUOTE(JSON_EXTRACT(payload, '$.new_price')) as price, JSON_UNQUOTE(JSON_EXTRACT(payload, '$.reason')) as change_reason FROM pricing_events WHERE product_pricing_id = 123 AND type = 'price_changed' AND performed_at >= NOW() - INTERVAL 30 DAY ORDER BY performed_at;

Conclusione

Il pattern "Event-Sourced Entity" è più di un semplice "salvare il vecchio valore". È un cambiamento di paradigma dal pensare in "stati" al pensare in "processi" e "cronologia".

Usalo quando:

  • Sono richiesti audit trail legislativi (farmaceutica, finanza, B2G).
  • Il processo di business è una sequenza di eventi significativi (ordine: creato → pagato → evaso → spedito).
  • Hai un bisogno critico di analytics approfondite su metriche chiave che cambiano (prezzi, scorte, tassi, valutazioni).

È un investimento nell'architettura che ripaga con trasparenza, flessibilità e profondità dei dati per il processo decisionale.

Tecnologie e argomenti

Tag dell'articolo

Nessun articolo corrisponde a questi filtri.

Hai un progetto o un'idea da discutere?

Parliamone ↗