Introduzione
Nell'ecosistema dello sviluppo software moderno, poche scelte architetturali hanno un impatto così pervasivo come la decisione di adottare un approccio event-driven. Non si tratta semplicemente di scegliere un message broker o sostituire chiamate REST con code Kafka: si tratta di ripensare il modo in cui i componenti del sistema comunicano, come si gestisce lo stato e come si ragiona sul tempo.
In questo articolo analizziamo i pattern fondamentali, i casi d'uso in cui un'architettura event-driven porta valore reale e — cosa spesso ignorata — gli anti-pattern e i costi nascosti che emergono solo in produzione. Tutto basato su esperienze dirette su sistemi in produzione con decine di milioni di eventi al giorno.
Cosa si intende davvero per "Event-Driven"
Il termine è abusato. "Event-driven" può significare cose molto diverse a seconda del contesto: dal semplice webhook HTTP, all'event sourcing puro, fino a sistemi di stream processing complessi. Prima di progettare qualsiasi cosa, è essenziale chiarire il livello di adozione desiderato.
"An event-driven architecture uses events to trigger and communicate between decoupled services. An event is a change in state, or an update." — AWS Architecture Guide
I tre pattern principali
- Event Notification: i servizi pubblicano notifiche di stato modificato senza includere il payload completo. I consumer interrogano la sorgente se necessario.
- Event-Carried State Transfer: gli eventi portano tutto lo stato necessario. Riduce il coupling ma aumenta la dimensione dei messaggi.
- Event Sourcing: lo stato dell'applicazione è derivato interamente da una sequenza di eventi immutabili. Massima flessibilità e auditability, ma complessità elevata.
Quando usarlo (e quando no)
L'architettura event-driven brilla in scenari con alta concorrenza, processi lunghi o asincroni, integrazione tra sistemi eterogenei e requisiti di auditability. Non è la scelta giusta per operazioni semplici CRUD, sistemi con requisiti di strong consistency transazionale o team con poca esperienza nel design distribuito.
Stack consigliato nel 2026
Per la messaggistica, Apache Kafka rimane il riferimento per volumi elevati e retention lunga.
RabbitMQ è preferibile per routing complesso con scambio di messaggi più brevi.
Per il stream processing, Apache Flink o Kafka Streams coprono la maggior parte dei casi.
# Esempio: Kafka producer PHP con rdkafka
$conf = new RdKafka\Conf();
$conf->set('metadata.broker.list', 'kafka:9092');
$producer = new RdKafka\Producer($conf);
$topic = $producer->newTopic('order.created');
$topic->produce(RD_KAFKA_PARTITION_UA, 0, json_encode([
'event_id' => uniqid('evt_', true),
'order_id' => $orderId,
'user_id' => $userId,
'timestamp' => time(),
]));
Sfide in produzione
La gestione degli errori è la parte più sottovalutata. In un sistema sincrono, una transazione fallisce e il chiamante lo sa immediatamente. In un sistema event-driven, un consumer può fallire silenziosamente, i messaggi possono essere processati più volte (at-least-once delivery) e il debugging di una sequenza di eventi distribuiti richiede strumenti di distributed tracing. Investire in OpenTelemetry fin dall'inizio non è opzionale.