event-driven
architecture.

Dai fondamentali alle sfide di produzione.

Un documento. Più consegne. Un solo effetto?

01 / understand02 / 22

un documento. una lunga attesa.

Il punto di partenza: elaborazione AI nella richiesta HTTP.

ClientUpload metadataBackend APIAttende il risultatoProcessingAI document indexing

Timeout · dipendenze a runtime · picchi senza buffer

Event-Driven ArchitectureDocumentUploaded / 01
01 / understand03 / 22

accettato ≠ completato.

L’API accetta il lavoro. Il worker lo completa in un secondo momento.

Backend API202 AcceptedRabbitMQBuffer durevoleWorkerElaboraPostgreSQLCOMMIT

Il client consulta lo stato del job; non attende l’elaborazione nella stessa richiesta.

Event-Driven ArchitectureDocumentUploaded / 01
01 / understand04 / 22

tre parole. tre ruoli.

La semantica conta prima del trasporto.

EVENT

È successo.

DocumentUploaded

Un fatto già avvenuto.

COMMAND

Fai questo.

ProcessDocument

Una richiesta a un destinatario.

MESSAGE

Trasporta.

Envelope + payload

Può contenere un event o un command.

Nel lab: il submit genera un fatto; l’indicizzatore reagisce a DocumentUploaded.

Event-Driven ArchitectureDocumentUploaded / 01
01 / understand05 / 22

quando REST è la scelta giusta.

Scegliere l’asincrono significa accettarne anche il costo.

Risposta immediata

  • Operazione breve e prevedibile
  • Risultato necessario per proseguire
  • Poche dipendenze, semplicità operativa

Il nostro upload

La richiesta di accettazione resta HTTP.

Solo il lavoro lungo attraversa il broker.

Sincrono e asincrono possono convivere.

Event-Driven ArchitectureDocumentUploaded / 01
02 / design06 / 22

chi fa cosa.

Le responsabilità sono separate; il contratto del messaggio le collega.

ProducerPubblica il fattoBrokerMemorizza e instradaConsumerEsegue il lavoro

RabbitMQ: producer → exchange → queue → consumer. L’exchange instrada; la queue conserva.

Event-Driven ArchitectureDocumentUploaded / 02
02 / design07 / 22

distribuire lavoro. diffondere fatti.

Competing consumers e publish/subscribe risolvono problemi diversi.

SHARED QUEUE

Un lavoro, un destinatario
per consegna.

QueueDocumentUploadedWorker AIndicizzaWorker BIndicizza
FAN-OUT EXCHANGE

Una copia per
sottoscrizione.

ExchangefanoutQueue AIndicizzazioneQueue BNotifiche
Event-Driven ArchitectureDocumentUploaded / 02
02 / design08 / 22

la vita di un messaggio.

Publisher confirm e consumer ACK proteggono due passaggi diversi.

01

Publish

Il producer invia

02

Confirm

Il broker accetta

03

Deliver

Il worker riceve

04

COMMIT

Il DB persiste

05

ACK

Il worker conferma

Durable queue + persistent message + publisher confirms.
ACK manuale dopo il lavoro. Nessuna garanzia globale implicita.

Event-Driven ArchitectureDocumentUploaded / 02
02 / design09 / 22

broker diversi. modelli diversi.

Non sono sostituti con semantiche identiche.

RABBITMQ

Routing + queue

Exchange, binding, consegne e ACK.

Il lab usa una queue classic durevole, non replicata.

APACHE KAFKA

Log partizionato

Retention, offset e replay.

Ordine per partizione; consumer group indipendenti.

SQS / SNS

Servizi gestiti

Queue e fan-out come servizi distinti.

Standard e FIFO hanno proprietà differenti.

Event-Driven ArchitectureDocumentUploaded / 02
03 / trade-offs10 / 22

spostiamo l’accoppiamento.

Meno dipendenze temporali. Più responsabilità distribuite.

GUADAGNI

Tempo e capacità.

  • Buffer per i picchi
  • Worker scalabili separatamente
  • Nuovi subscriber senza bloccare l’API
COSTI

Stato e recupero.

  • Eventual consistency e ordine
  • Retry, duplicati, recovery
  • Correlazione per capire la storia
Event-Driven ArchitectureDocumentUploaded / 03
04 / break11 / 22

quante volte arriva?

La delivery guarantee non è la business guarantee.

AT-MOST-ONCE

0 oppure 1

Possibile perdita.

Un ACK anticipato può perdere il lavoro dopo un crash.

AT-LEAST-ONCE

1 oppure più

Duplicati possibili.

Il lab usa ACK manuali e deve tollerare la redelivery.

EXACTLY-ONCE

Entro un confine.

Chiedere sempre quale.

Transazioni del broker non coprono automaticamente API e DB esterni.

Event-Driven ArchitectureDocumentUploaded / 04
04 / break12 / 22

il crash nel punto sbagliato.

Il DB ha finito. Il broker non lo sa.

01

Deliver

Unacked

02

COMMIT

Effetto persistito

03 / ×

Crash

Prima dell’ACK

04

Redeliver

Stesso messaggio

Connessione chiusa → il broker rimette in coda la consegna non confermata.
Senza idempotenza: l’effetto può ripetersi.

Event-Driven ArchitectureDocumentUploaded / 04
04 / investigate13 / 22

stesso fatto. quale consegna?

Tre identità rendono leggibile la dimostrazione.

LOGICAL EVENT

eventId

Resta stabile.

BROKER MESSAGE

publicationId

Cambia al republish.

RECEIPT

delivery ID

Cambia a ogni ricezione.

Republish ≠ redelivery.
Nel secondo caso: stessa publicationId, RabbitMQ redelivered=true.

Event-Driven ArchitectureDocumentUploaded / 04
04 / investigate14 / 22

“prima controllo” non basta.

Due consumer possono osservare lo stesso “non ancora processato”.

if (!(await alreadyProcessed(event.id))) {
  await applyBusinessOperation(event);
  await markAsProcessed(event.id);
}

A legge: assente

B legge: assente

A + B applicano l’effetto

Check e write separati lasciano una race condition.

Event-Driven ArchitectureDocumentUploaded / 04
04 / fix15 / 22

un solo commit.

Il vincolo unico decide chi può applicare l’effetto.

BEGIN;
INSERT INTO processed_events (...)
  ON CONFLICT DO NOTHING
  RETURNING event_id;

-- solo se la claim è riuscita:
UPDATE documents SET operations = operations + 1;
INSERT INTO operations (...);
COMMIT;
1

effetto DB persistito

Claim + effetto nella stessa transazione.

Una claim concorrente attende la decisione della prima transazione.

Unicità: (scenario_id, event_id). Dopo COMMIT, il consumer invia ACK.

Event-Driven ArchitectureDocumentUploaded / 04
04 / generalize16 / 22

i retry hanno bisogno di un limite.

Un errore transitorio e un poison message non si trattano allo stesso modo.

Retry

  • Backoff e limite dei tentativi
  • Nessun loop immediato senza fine
  • Nel lab: fino a 3 tentativi applicativi

Dead Letter Queue

  • Isola i messaggi non elaborabili
  • Indaga prima del replay
  • Non equivale a lavoro completato
Event-Driven ArchitectureDocumentUploaded / 04
05 / live lab17 / 22

guardiamo gli effetti.

Prima la normalità. Poi lo stesso evento, due volte.

01 / HAPPY PATH

Publish new event

1 operazione · 100 unità simulate · ACK sent

02 / NAIVE DUPLICATE

Republish selected event

2 operazioni · 200 unità simulate · nessuna redelivery

Apri il lab →
Event-Driven ArchitectureDocumentUploaded / 05
05 / live lab18 / 22

ora proteggete il database.

Una nuova run elimina la contaminazione tra modalità.

03 / IDEMPOTENT

Ripeti il fatto.

2 delivery · 1 effetto · 1 duplicate skipped

04 / CONCURRENT

Due consumer. Una claim.

Start second consumer → Publish concurrent pair

Passa alla modalità Idempotent →
Event-Driven ArchitectureDocumentUploaded / 05
05 / live lab19 / 22

il crash. quello vero.

COMMIT riuscito. Nessun ACK. Un nuovo processo riceve lo stesso messaggio.

05 / REAL PROCESS EXIT

Arm crash → Publish

Exit code 42 · riavvio automatico · redelivered=true

GUARDA IL DATABASE

L’effetto resta uno.

Stessa publicationId · nuovo PID · duplicate detected · ACK sent

Esegui il crash nel lab →
Event-Driven ArchitectureDocumentUploaded / 05
06 / AWS20 / 22

stessi problemi. altri confini.

Il cloud gestisce infrastruttura; l’applicazione gestisce la semantica.

SQS

Queue

Buffer, visibility timeout, redelivery.

SNS

Fan-out

Un topic, più sottoscrizioni.

EVENTBRIDGE

Routing

Event bus, regole, target.

SQS: il messaggio si elimina dopo il successo.
Con Lambda: event source mapping in polling, batch e retry da progettare.

Event-Driven ArchitectureDocumentUploaded / 06
06 / AWS21 / 22

una possibile versione cloud.

Solo confronto teorico: nessuna risorsa AWS richiesta.

S3ObjectCreatedEventBridgeRule → queueSQSVisibility timeoutLambdaTriggered computeDynamoDBTransactWriteItemsSNS → queue indicizzazione + queue notifiche: due subscriber indipendenti.

Conditional write per la claim; transazione per claim + effetto.
Account, Region e limiti del servizio definiscono il confine.

Event-Driven ArchitectureDocumentUploaded / 06

exactly once?

Can we really guarantee that an event is processed exactly once?

Delivery del messaggio.

Esecuzione del consumer.

Effetto di business.

Il lab protegge un effetto PostgreSQL. Non ogni sistema esterno.
Prossimi passi: Inbox per deduplicare, Outbox per pubblicare con affidabilità.