Workflow basati su eventi vs API request-response per processi di lunga durata
Confronta workflow basati su eventi e API request-response per processi di lunga durata, concentrandosi su approvazioni, timer, retry e tracce di audit nelle app business.

Perché i processi di lunga durata sono complicati nelle app business
Un processo è “di lunga durata” quando non può concludersi in un singolo passaggio veloce. Può durare minuti, ore o giorni perché dipende dalle persone, dal tempo o da sistemi esterni. Tutto ciò che prevede approvazioni, passaggi di consegne e attese rientra in questa categoria.
Qui è dove il pensiero semplice da API request-response comincia a rompersi. Una chiamata API è pensata per uno scambio breve: invii una richiesta, ricevi una risposta e vai avanti. Le attività lunghe sono più come una storia a capitoli. Devi mettere in pausa, ricordare esattamente dove sei e continuare più tardi senza indovinare.
Lo vedi nelle app business quotidiane: approvazioni di acquisti che richiedono manager e finanza, onboarding di dipendenti che attende controlli documentali, rimborsi che dipendono da un provider di pagamento o richieste di accesso che devono essere revisionate e poi applicate.
Quando i team trattano un processo lungo come una singola chiamata API, compaiono alcuni problemi prevedibili:
- L'app perde lo stato dopo un riavvio o un deploy e non riesce a riprendere in modo affidabile.
- I retry generano duplicati: un secondo pagamento, una seconda email, una doppia approvazione.
- La proprietà dell'azione diventa confusa: nessuno sa se deve agire il richiedente, un manager o un job di sistema.
- Il supporto non ha visibilità e non può rispondere “dove è bloccato?” senza scavare nei log.
- La logica di attesa (timer, promemoria, scadenze) finisce come script di background fragili.
Uno scenario concreto: un dipendente richiede accesso a un software. Il manager approva rapidamente, ma l'IT impiega due giorni per fornirlo. Se l'app non può mantenere lo stato del processo, inviare promemoria e riprendere in sicurezza, ottieni follow-up manuali, utenti confusi e lavoro extra.
Ecco perché la scelta tra workflow basati su eventi e API request-response è importante per i processi aziendali di lunga durata.
Due modelli mentali: chiamate sincrone vs eventi nel tempo
Il confronto più semplice si riassume in una domanda: il lavoro termina mentre l'utente aspetta o continua dopo che se ne è andato?
Un'API request-response è uno scambio singolo: una chiamata in, una risposta out. Si adatta ai lavori che si completano velocemente e in modo prevedibile, come creare un record, calcolare un preventivo o controllare l'inventario. Il server esegue il lavoro, restituisce successo o errore e l'interazione è finita.
Un workflow guidato da eventi è una serie di reazioni nel tempo. Succede qualcosa (un ordine viene creato, un manager approva, scade un timer) e il workflow passa al passo successivo. Questo modello si adatta a lavori che includono passaggi di consegna, attese, retry e promemoria.
La differenza pratica è lo stato.
Con request-response, lo stato spesso vive nella richiesta corrente più la memoria del server fino a che la risposta non viene inviata. Nei workflow basati su eventi, lo stato deve essere memorizzato (per esempio in PostgreSQL) così il processo può riprendere più tardi.
Cambia anche la gestione dei fallimenti. Request-response di solito gestisce i fallimenti restituendo un errore e chiedendo al client di ritentare. I workflow registrano il fallimento e possono ritentare in sicurezza quando le condizioni migliorano. Possono anche registrare ogni passo come evento, il che rende la storia più facile da ricostruire.
Un esempio semplice: “Invia nota spese” può essere sincrono. “Ottenere approvazione, aspettare 3 giorni, ricordare al manager, poi pagare” non lo è.
Approvazioni: come ogni approccio gestisce le decisioni umane
Le approvazioni sono il punto in cui il lavoro di lunga durata diventa reale. Un passo di sistema finisce in millisecondi, ma una persona può rispondere in due minuti o in due giorni. La scelta di progettazione chiave è se modellare quell'attesa come un processo in pausa o come un nuovo messaggio che arriva più tardi.
Con le API request-response, le approvazioni spesso prendono una forma scomoda:
- Bloccante (non pratico)
- Polling (il client chiede “hai approvato?” più e più volte)
- Callback/webhook (il server ti richiama più tardi)
Tutto ciò può funzionare, ma aggiunge impalcature solo per collegare il “tempo umano” con il “tempo API”.
Con gli eventi, l'approvazione si legge come una storia. L'app registra qualcosa come “ExpenseSubmitted”, poi più tardi riceve “ExpenseApproved” o “ExpenseRejected”. Il motore del workflow (o la tua macchina a stati) muove il record avanti solo quando arriva il prossimo evento. Questo corrisponde a come la maggior parte delle persone pensa già ai passaggi aziendali: invia, revisiona, decidi.
La complessità emerge rapidamente con più approvatori e regole di escalation. Potresti richiedere sia il manager che la finanza, ma permettere anche a un manager senior di sovrascrivere. Se non modelli chiaramente quelle regole, il processo diventa difficile da capire e ancora più difficile da controllare.
Un modello semplice di approvazione che scala
Un pattern pratico è mantenere un singolo record di “request” e memorizzare le decisioni separatamente. In questo modo puoi supportare molti approvatori senza riscrivere la logica centrale.
Cattura alcuni dati come record di prima classe:
- La richiesta di approvazione: cosa viene approvato e lo stato corrente
- Decisioni individuali: chi ha deciso, approva/rifiuta, timestamp, motivo
- Gli approvatori richiesti: ruolo o persona e qualsiasi regola d'ordine
- Regole di esito: “uno qualsiasi”, “maggioranza”, “tutti richiesti”, “override permesso”
Qualunque implementazione tu scelga, memorizza sempre chi ha approvato cosa, quando e perché come dato, non come una semplice riga di log.
Timer e attese: promemoria, scadenze ed escalation
L'attesa è dove le attività lunghe cominciano a sembrare disordinate. Le persone vanno in pausa pranzo, i calendari si riempiono e “ti rispondiamo” diventa “chi lo possiede ora?”. Questa è una delle differenze più chiare tra workflow basati su eventi e API request-response.
Con le API request-response il tempo è scomodo. Le chiamate HTTP hanno timeout, quindi non puoi tenere una richiesta aperta per due giorni. I team spesso finiscono con pattern come polling, un job schedulato che scansiona il database o script manuali quando qualcosa è scaduto. Questi possono funzionare, ma la logica di attesa vive fuori dal processo. È facile perdere i casi limite, per esempio cosa succede quando il job viene eseguito due volte o quando il record cambia subito prima dell'invio del promemoria.
I workflow trattano il tempo come un passo normale. Puoi dire: aspetta 24 ore, invia un promemoria, poi aspetta fino a 48 ore totali ed escala a un altro approvatore. Il sistema mantiene lo stato, quindi le scadenze non sono nascoste in un progetto separato “cron + query”.
Una regola di approvazione semplice potrebbe dire:
Dopo che una nota spese è stata inviata, aspetta 1 giorno. Se lo stato è ancora “Pending”, manda un messaggio al manager. Dopo 2 giorni, se è ancora pending, riassegna al responsabile del manager e registra l'escalation.
Il dettaglio chiave è cosa fai quando il timer scatta ma il mondo è cambiato. Un buon workflow ricontrolla sempre lo stato corrente prima di agire:
- Carica lo stato più recente
- Conferma che è ancora pending
- Conferma che l'assegnatario è ancora valido (i cambi di team succedono)
- Registra cosa hai deciso e perché
Retry e recupero dai fallimenti senza azioni duplicate
I retry servono quando qualcosa fallisce per motivi che non puoi controllare completamente: un gateway di pagamento va in timeout, un provider email restituisce un errore temporaneo, o la tua app salva il passo A ma va in crash prima del passo B. Il pericolo è semplice: ritenti e fai l'azione due volte.
Con le API request-response, il pattern comune è che il client chiama un endpoint, aspetta e se non riceve un successo chiaro ritenta. Per rendere questo sicuro, il server deve trattare le chiamate ripetute come la stessa intenzione.
Una soluzione pratica è una chiave di idempotenza: il client invia un token unico come pay:invoice-583:attempt-1. Il server memorizza l'esito per quella chiave e restituisce lo stesso risultato per le ripetizioni. Questo evita doppi addebiti, ticket duplicati o approvazioni ripetute.
I workflow basati su eventi hanno un diverso tipo di rischio di duplicazione. Gli eventi spesso vengono consegnati almeno una volta, il che significa che possono apparire duplicati anche quando tutto funziona. I consumer devono deduplicare: registra l'ID dell'evento (o una chiave di business come invoice_id + step) e ignora le ripetizioni. Questa è una differenza fondamentale nei pattern di orchestrazione: request-response si concentra su replay sicuri delle chiamate, mentre gli eventi su replay sicuri dei messaggi.
Alcune regole di retry funzionano bene in entrambi i modelli:
- Usa backoff (per esempio 10s, 30s, 2m).
- Imposta un limite massimo di tentativi.
- Separa errori temporanei (ritenta) da errori permanenti (fallire subito).
- Invia i fallimenti ripetuti in uno stato “richiede attenzione”.
- Registra ogni tentativo così puoi spiegare cosa è successo dopo.
I retry dovrebbero essere espliciti nel processo, non comportamento nascosto. Così rendi i fallimenti visibili e correggibili.
Tracce di audit: rendere il processo spiegabile
Una traccia di audit è il tuo file del “perché”. Quando qualcuno chiede “Perché questa nota spese è stata rifiutata?” dovresti poter rispondere senza indovinare, anche mesi dopo. Questo conta sia per workflow basati su eventi sia per API request-response, ma il lavoro appare diverso.
Per ogni processo di lunga durata, registra i fatti che ti permettono di riprodurre la storia:
- Attore: chi l'ha fatto (utente, servizio o timer di sistema)
- Tempo: quando è accaduto (con fuso orario)
- Input: cosa si sapeva allora (importo, fornitore, soglie di policy, approvazioni)
- Output: quale decisione o azione è avvenuta (approvato, rifiutato, pagato, ritentato)
- Versione della regola: quale versione della policy/ logica è stata usata
I workflow basati su eventi possono facilitare l'audit perché ogni passo naturalmente produce un evento come “ManagerApproved” o “PaymentFailed”. Se memorizzi quegli eventi con payload e attore, ottieni una timeline pulita. La chiave è mantenere gli eventi descrittivi e conservarli in modo interrogabile per caso.
Le API request-response possono essere comunque auditabili, ma la storia spesso è sparsa tra servizi. Un endpoint logga “approved”, un altro logga “payment requested” e un terzo logga “retry succeeded”. Se ognuno usa formati o campi diversi, l'audit diventa lavoro da detective.
Una soluzione semplice è un “case ID” condiviso (detto anche correlation ID). È un identificatore che alleghi a ogni richiesta, evento e record di database per l'istanza del processo, come “EXP-2026-00173”. Così puoi tracciare l'intero percorso attraverso i passaggi.
Scegliere l'approccio giusto: punti di forza e compromessi
La scelta migliore dipende dal fatto se hai bisogno di una risposta immediata o che il processo continui per ore o giorni.
Request-response funziona bene quando il lavoro è breve e le regole sono semplici. Un utente invia un modulo, il server lo valida, salva i dati e restituisce successo o errore. È anche adatto per azioni chiare e a passo unico come creare, aggiornare o verificare permessi.
Comincia a mostrare limiti quando una “singola richiesta” si trasforma in molti passi: aspettare un'approvazione, chiamare più sistemi esterni, gestire timeout o diramare il flusso in base a cosa succede dopo. O mantieni una connessione aperta (fragile), o sposti attesa e retry in job di background difficili da ragionare.
I workflow basati su eventi brillano quando il processo è una storia nel tempo. Ogni passo reagisce a un nuovo evento (approvato, rifiutato, timer scaduto, pagamento fallito) e decide cosa fare dopo. Questo facilita mettere in pausa, riprendere, ritentare e mantenere una traccia chiara del perché il sistema ha agito così.
Ci sono compromessi reali:
- Semplicità vs durabilità: request-response è più semplice per cominciare, event-driven è più sicuro per ritardi lunghi.
- Stile di debugging: request-response segue una linea retta, i workflow richiedono spesso il tracciamento tra passi.
- Tooling e abitudini: gli eventi richiedono buon logging, correlation ID e modelli di stato chiari.
- Gestione dei cambiamenti: i workflow evolvono e diramano; i design event-driven tendono a gestire meglio nuovi percorsi se ben modellati.
Un esempio pratico: una nota spese che necessita approvazione manageriale, poi revisione finanza e infine pagamento. Se il pagamento fallisce, vuoi retry senza doppio addebito. Questo è naturalmente event-driven. Se si tratta solo di “invia nota spese” con controlli rapidi, request-response spesso basta.
Passo dopo passo: progettare un processo di lunga durata che sopravvive ai ritardi
I processi di lunga durata falliscono in modi noiosi: una scheda del browser si chiude, un server si riavvia, un'approvazione resta per tre giorni o un provider di pagamento va in timeout. Progetta per questi ritardi fin dall'inizio, indipendentemente dal modello che preferisci.
Comincia definendo un piccolo insieme di stati che puoi memorizzare e riprendere. Se non riesci a indicare lo stato attuale nel database, non hai veramente un workflow riavviabile.
Una sequenza di progettazione semplice
- Definisci i confini: trigger di avvio, condizione di fine e alcuni stati chiave (In attesa di approvazione, Approvato, Rifiutato, Scaduto, Completato).
- Nomina eventi e decisioni: scrivi cosa può succedere nel tempo (Submitted, Approved, Rejected, TimerFired, RetryScheduled). Mantieni i nomi degli eventi al passato.
- Scegli i punti di attesa: identifica dove il processo si mette in pausa per un umano, un sistema esterno o una scadenza.
- Aggiungi regole di timer e retry per ogni step: decidi cosa succede quando passa il tempo o una chiamata fallisce (backoff, tentativi massimi, escalation, desistere).
- Definisci come il processo riprende: ad ogni evento o callback carica lo stato salvato, verifica che sia ancora valido e poi passa allo stato successivo.
Per sopravvivere ai riavvii, persisti i dati minimi necessari per continuare in sicurezza. Memorizza abbastanza per rieseguire senza indovinare:
- ID dell'istanza del processo e stato corrente
- Chi può agire dopo (assegnatario/ruolo) e cosa ha deciso
- Scadenze (due_at, remind_at) e livello di escalation
- Metadati di retry (conteggio tentativi, ultimo errore, next_retry_at)
- Chiave di idempotenza o flag “già fatto” per effetti collaterali (inviare un messaggio, addebitare una carta)
Se puoi ricostruire “dove siamo” e “cosa è permesso fare dopo” dai dati salvati, i ritardi smettono di spaventare.
Errori comuni e come evitarli
I processi di lunga durata spesso si rompono solo quando arrivano utenti reali. Un'approvazione dura due giorni, un retry scatta nel momento sbagliato e finisci con un doppio pagamento o una traccia di audit mancante.
Errori comuni:
- Tenere una richiesta HTTP aperta in attesa di un'approvazione. Scade, occupa risorse server e dà all'utente l'illusione che “qualcosa stia succedendo”.
- Ritentare chiamate senza idempotenza. Un problema di rete si trasforma in fatture duplicate, email duplicate o transizioni “Approved” ripetute.
- Non memorizzare lo stato del processo. Se lo stato vive in memoria, un riavvio lo azzera. Se vive solo nei log, non puoi continuare in modo affidabile.
- Costruire una traccia di audit confusa. Eventi con orologi e formati diversi rendono la timeline inaffidabile durante un incidente o una verifica di conformità.
- Mescolare async e sync senza una singola fonte di verità. Un sistema dice “Paid”, un altro “Pending” e nessuno sa quale sia corretto.
Un esempio semplice: una nota spese è approvata in chat, arriva un webhook in ritardo e l'API di pagamento viene ritentata. Senza stato persistente e idempotenza, il retry può inviare il pagamento due volte e i tuoi record non spiegheranno chiaramente perché.
La maggior parte delle correzioni si riduce a essere espliciti:
- Persisti le transizioni di stato (Requested, Approved, Rejected, Paid) in un database, con chi/che cosa le ha cambiate.
- Usa chiavi di idempotenza per ogni effetto collaterale esterno (pagamenti, email, ticket) e memorizza il risultato.
- Separa “accetta la richiesta” da “completa il lavoro”: rispondi rapidamente, poi completa il workflow in background.
- Standardizza le timestamp (UTC), aggiungi correlation ID e registra sia la richiesta sia l'esito.
Checklist rapida prima di costruire
Il lavoro di lunga durata riguarda meno una chiamata perfetta e più rimanere corretti dopo ritardi, persone e guasti.
Scrivi cosa significa “sicuro da continuare” per il tuo processo. Se l'app si riavvia a metà, dovresti poter ripartire dall'ultimo passo noto senza indovinare.
Una checklist pratica:
- Definisci come il processo riprende dopo un crash o un deploy. Quale stato è salvato e cosa viene eseguito dopo?
- Dai a ogni istanza una chiave di processo unica (es. ExpenseRequest-10482) e un modello di stato chiaro (Submitted, Waiting for Manager, Approved, Paid, Failed).
- Tratta le approvazioni come record, non solo risultati: chi ha approvato o rifiutato, quando e il motivo o commento.
- Mappa le regole di attesa: promemoria, scadenze, escalation, scadenze per l'expiration. Nomina un proprietario per ogni timer (manager, finanza, sistema).
- Pianifica la gestione degli errori: i retry devono essere limitati e sicuri, e ci dovrebbe essere uno stato “needs review” dove una persona può correggere i dati o autorizzare un nuovo tentativo.
Un test di sanità: immagina che un provider di pagamento vada in timeout dopo che hai già addebitato la carta. Il tuo design dovrebbe prevenire il doppio addebito, pur permettendo al processo di concludersi.
Esempio: approvazione spese con scadenza e retry pagamento
Scenario: un dipendente invia una ricevuta taxi di $120 per rimborso. Serve l'approvazione del manager entro 48 ore. Se approvato, il sistema paga al dipendente. Se il pagamento fallisce, ritenta in sicurezza e lascia traccia chiara.
Walkthrough request-response
Con le API request-response, l'app spesso si comporta come una conversazione che deve continuare a controllare.
Il dipendente preme Invia. Il server crea un record di rimborso con stato “Pending approval” e restituisce un ID. Il manager riceve una notifica, ma l'app del dipendente di solito deve fare polling per vedere se qualcosa è cambiato, per esempio: “GET reimbursement status by ID.”
Per far rispettare la scadenza di 48 ore o esegui un job schedulato che scansiona le richieste scadute, o memorizzi un timestamp di scadenza e lo verifichi durante i poll. Se il job è in ritardo, gli utenti vedono stato obsoleto.
Quando il manager approva, il server cambia lo stato in “Approved” e chiama il provider di pagamento. Se Stripe restituisce un errore temporaneo, il server deve decidere se ritentare subito, più tardi o fallire. Senza attenzione alle chiavi di idempotenza, un retry può creare un doppio pagamento.
Walkthrough event-driven
In un modello event-driven, ogni cambiamento è un fatto registrato.
Il dipendente invia, producendo un evento “ExpenseSubmitted”. Un workflow parte e aspetta o “ManagerApproved” o un evento timer “DeadlineReached” dopo 48 ore. Se il timer scatta prima, il workflow registra un risultato “AutoRejected” e il motivo.
All'approvazione, il workflow registra “PayoutRequested” e tenta il pagamento. Se Stripe va in timeout, registra “PayoutFailed” con un codice errore, programma un retry (ad esempio tra 15 minuti) e registra “PayoutSucceeded” solo una volta usando una chiave di idempotenza.
Quello che l'utente vede rimane semplice:
- In attesa di approvazione (48 ore rimanenti)
- Approvato, in pagamento
- Retry di pagamento programmato
- Pagato
La traccia di audit si legge come timeline: submitted, approved, deadline controllata, payout tentato, fallito, ritentato, pagato.
Prossimi passi: trasformare il modello in un'app funzionante
Scegli un processo reale e costruiscilo end-to-end prima di generalizzare. Approvazione spese, onboarding e gestione refund sono buoni punti di partenza perché includono passi umani, attese e percorsi di errore. Mantieni l'obiettivo piccolo: un percorso principale e le due eccezioni più comuni.
Scrivi il processo come stati ed eventi, non come schermate. Per esempio: “Submitted” -> “ManagerApproved” -> “PaymentRequested” -> “Paid”, con rami come “ApprovalRejected” o “PaymentFailed”. Quando vedi chiaramente i punti di attesa e gli effetti collaterali, la scelta tra workflow basati su eventi e API request-response diventa pratica.
Decidi dove risiede lo stato del processo. Un database può bastare se il flusso è semplice e puoi far rispettare gli aggiornamenti in un solo punto. Un motore di workflow aiuta quando hai bisogno di timer, retry e branching, perché tiene traccia di cosa deve accadere dopo.
Aggiungi i campi di audit fin da subito. Memorizza chi ha fatto cosa, quando è successo e perché (commento o codice motivo). Quando qualcuno chiede “Perché questo pagamento è stato ritentato?” vuoi una risposta chiara senza scavare nei log.
Se stai costruendo questo tipo di workflow su una piattaforma no-code, AppMaster (appmaster.io) è un'opzione dove puoi modellare i dati in PostgreSQL e costruire la logica di processo visivamente, il che può rendere più semplice mantenere coerenti approvazioni e tracce di audit su web e mobile.
FAQ
Usa request-response quando il lavoro termina velocemente e prevedibilmente mentre l'utente aspetta, come creare un record o validare un modulo. Usa un workflow basato su eventi quando il processo si estende per minuti o giorni, include approvazioni umane o necessita di timer, retry e ripresa sicura dopo riavvii.
Le attività lunghe non si adattano a una singola richiesta HTTP perché le connessioni scadono, i server si riavviano e il lavoro spesso dipende da persone o sistemi esterni. Trattandolo come una sola chiamata, di solito perdi lo stato, crei duplicati nei retry e finisci con script di background sparsi per gestire le attese.
Una buona impostazione predefinita è persistere uno stato chiaro del processo nel database e avanzarlo solo tramite transizioni esplicite. Memorizza l'ID dell'istanza del processo, lo stato corrente, chi può agire dopo e le timestamp chiave in modo da poter riprendere in sicurezza dopo deploy, crash o ritardi.
Modella le approvazioni come uno step in pausa che riprende quando arriva una decisione, invece di bloccare o fare polling continuo. Registra ogni decisione come dato (chi ha deciso, quando, approva/rifiuta e motivo) così il workflow può procedere in modo prevedibile e auditabile.
Il polling può funzionare per casi semplici, ma aggiunge rumore e ritardi perché il client continua a chiedere “è finito?”. Un comportamento di default migliore è inviare una notifica al cambiamento e lasciare che il client si aggiorni su richiesta, mentre il server rimane la fonte di verità per lo stato.
Tratta il tempo come parte del processo memorizzando scadenze e orari di promemoria, poi ricontrolla lo stato corrente quando il timer scatta prima di agire. Questo evita di inviare promemoria dopo che qualcosa è già stato approvato e mantiene le escalation coerenti anche se i job sono in ritardo o vengono eseguiti due volte.
Parti con chiavi di idempotenza per ogni effetto collaterale come addebitare una carta o inviare un'email, e memorizza il risultato per quella chiave. I retry diventano sicuri perché ripetere la stessa intenzione restituisce lo stesso risultato invece di ripetere l'azione.
Assumi che i messaggi possano essere consegnati più di una volta e progetta i consumer per deduplicare. Un approccio pratico è memorizzare l'ID dell'evento (o una chiave di business per lo step) e ignorare le ripetizioni in modo che una replay non inneschi la stessa azione due volte.
Cattura una sequenza di fatti: attore, timestamp, input al momento, l'esito e la versione della regola o politica usata. Assegna anche un singolo case ID o correlation ID a tutto ciò che riguarda quel processo così che il supporto possa rispondere “dove è bloccato?” senza scavare log non correlati.
Mantieni un record di richiesta come “case”, memorizza le decisioni separatamente e guida le modifiche di stato tramite transizioni persistenti che possono essere riavviate. In uno strumento no-code come AppMaster puoi modellare i dati in PostgreSQL e implementare la logica degli step in modo visuale, il che aiuta a mantenere coerenti approvazioni, retry e campi di audit nell'app.


