7 min

Integrazione sicura di API di terze parti: retry, timeout e circuit breaker

Integrazione sicura di API di terze parti per mantenere l'app reattiva durante outage. Scopri timeout, retry, circuit breaker e controlli rapidi.

Integrazione sicura di API di terze parti: retry, timeout e circuit breaker

Perché le API di terze parti possono bloccare i tuoi workflow principali

Un'API di terze parti può guastarsi in modi che non sembrano un classico "down". Il problema più comune è la lentezza: le richieste si bloccano, le risposte arrivano in ritardo e la tua app continua ad aspettare. Se quelle chiamate sono sulla strada critica, un piccolo intoppo esterno si accumula all'interno del tuo sistema.

È così che un rallentamento locale diventa un blackout completo. Thread o worker restano in attesa, le code crescono, le transazioni sul database restano aperte più a lungo e le nuove richieste iniziano a scadere. Presto anche pagine che non usano l'API esterna sembrano rotte perché il sistema è sovraccarico di lavoro in attesa.

L'impatto è concreto. Un identity provider instabile blocca registrazioni e accessi. Un timeout del gateway di pagamento congela il checkout, lasciando gli utenti incerti se sono stati addebitati. Un ritardo nei messaggi ferma i reset password e le conferme d'ordine, causando una seconda ondata di retry e ticket di supporto.

L'obiettivo è semplice: isolare i guasti esterni in modo che i workflow core continuino a muoversi. Questo può significare permettere a un utente di piazzare un ordine mentre confermi il pagamento dopo, o consentire la registrazione anche se l'email di benvenuto fallisce.

Una metrica pratica di successo: quando un provider è lento o giù, la tua app dovrebbe comunque rispondere in modo rapido e chiaro, e l'area interessata dovrebbe rimanere piccola. Per esempio, la maggior parte delle richieste core termina ancora entro il tuo budget di latenza normale, gli errori restano confinati alle funzionalità che dipendono davvero da quell'API, gli utenti vedono uno stato chiaro (in coda, in attesa, riprova più tardi) e il recupero avviene automaticamente quando il provider torna.

I modi di guasto per cui dovresti pianificare

La maggior parte dei guasti è prevedibile, anche se non è prevedibile quando avverrà. Nominali subito e puoi decidere cosa ritentare, cosa fermare e cosa mostrare all'utente.

Categorie comuni:

  • Picchi di latenza (richieste che improvvisamente impiegano 10x più tempo)
  • Errori transitori del server o di rete (timeout, 502/503, reset di connessione)
  • Limiti di rate e esaurimento quota (429, limiti giornalieri)
  • Problemi di autenticazione e permessi (chiavi scadute, accesso revocato)
  • Dati errati o sorprendenti (campi mancanti, formati sbagliati, risposte parziali)

Non tutti gli errori sono uguali. I problemi transitori spesso vale la pena ritentarli perché la chiamata successiva può avere successo (interruzioni di rete, timeout, 502/503 e alcuni 429 dopo attesa). I problemi permanenti di solito non si risolvono da soli (credenziali non valide, endpoint sbagliati, richieste malformate, negazioni di permesso).

Trattare ogni errore allo stesso modo trasforma un piccolo incidente in downtime. Ritentare guasti permanenti spreca tempo, consuma i limiti di rate più velocemente e crea un arretrato che rallenta tutto il resto. Non ritentare mai i guasti transitori costringe gli utenti a ripetere azioni e fa perdere lavoro che avrebbe potuto completarsi poco dopo.

Presta particolare attenzione ai workflow dove una pausa sembra una rottura: checkout, login, reset password e notifiche (email/SMS/push). Un picco di 2 secondi in un'API di marketing è fastidioso. Un picco di 2 secondi nell'autorizzazione di pagamento blocca il fatturato.

Un test utile è: "Questa chiamata deve terminare per completare il compito principale dell'utente adesso?" Se sì, servono timeout stretti, retry attenti e un percorso di errore chiaro. Se no, spostala in una coda e mantieni l'app reattiva.

Timeout: scegli un limite e rispettalo

Un timeout è il tempo massimo che sei disposto ad aspettare prima di fermarti e andare avanti. Senza un limite chiaro, un provider lento può accumulare richieste in attesa e bloccare il lavoro importante.

Aiuta separare due tipi di attesa:

  • Connect timeout: quanto a lungo tenterai di stabilire la connessione.
  • Read timeout: quanto a lungo aspetterai una risposta dopo la connessione.

Scegliere i numeri non è questione di perfezione. Si tratta di allinearsi alla pazienza umana e al tuo workflow.

  • Se un utente sta guardando uno spinner, di solito hai bisogno di una risposta veloce e di un passo successivo chiaro.
  • Se è un job in background (per esempio sincronizzare fatture durante la notte), puoi permettere più tempo, ma deve comunque avere un tetto per non restare appeso per sempre.

Un modo pratico per scegliere i timeout è partire dall'esperienza:

  • Quanto può aspettare un utente prima che tu debba mostrare un messaggio chiaro?
  • Se questa chiamata fallisce adesso, puoi riprovarci dopo o usare un fallback?
  • Quante di queste chiamate vengono eseguite al picco?

Il compromesso è reale. Troppo lungo e tieni occupati thread, worker e connessioni al DB. Troppo corto e crei falsi fallimenti e scateno retry inutili.

Retry che non peggiorano gli outage

I retry aiutano quando un errore è probabilmente temporaneo: un breve problema di rete, un hiccup DNS o un 500/502/503 occasionale. In questi casi, un secondo tentativo può avere successo e gli utenti non se ne accorgeranno.

Il rischio è una tempesta di retry. Quando molti client falliscono insieme e ritentano in massa, possono sovraccaricare il provider (e i tuoi worker). Backoff e jitter prevengono questo comportamento.

Un budget di retry mantiene le cose sobrie. Mantieni pochi tentativi e limita il tempo totale in modo che i workflow core non restino bloccati aspettando qualcun altro.

Una ricetta di default sicura per i retry

  • Ritenta solo poche volte (spesso 1-3 tentativi totali, a seconda del flusso).
  • Usa exponential backoff (per esempio 200ms, 500ms, 1s) più jitter casuale.
  • Limita il tempo totale speso in retry (spesso alcuni secondi nei flussi utente).
  • Usa un timeout per ogni tentativo invece di un unico timeout lungo per tutti i tentativi.

Non ritentare errori cliente prevedibili come 400/422 (validazione), 401/403 (auth) o 404. Quelli quasi sempre falliranno di nuovo e aggiungono solo carico.

Un altro guardrail: ritenta le scritture (POST/PUT) solo quando hai idempotenza, altrimenti rischi addebiti doppi o record duplicati.

Idempotenza: rendi i retry sicuri per i workflow reali

Idempotenza significa poter eseguire la stessa richiesta due volte e ottenere lo stesso risultato finale. Questo è importante perché i retry sono normali: le reti cadono, i server si riavviano e i client scadono. Senza idempotenza, un "utile" retry crea duplicati e problemi reali con soldi.

Immagina il checkout: l'API di pagamento è lenta, la tua app va in timeout e ritenti. Se la prima chiamata era andata a buon fine, il retry potrebbe causare un secondo addebito. Lo stesso rischio vale per azioni come creare un ordine, avviare un abbonamento, inviare un'email/SMS, emettere un rimborso o creare un ticket di supporto.

La soluzione è allegare una chiave di idempotenza (o un request ID) a ogni chiamata che "fa qualcosa". Deve essere unica per l'azione dell'utente, non per il tentativo. Il provider (o il tuo servizio) usa quella chiave per rilevare duplicati e restituire lo stesso esito invece di eseguire nuovamente l'azione.

Tratta la chiave di idempotenza come parte del modello dati, non come un header che speri nessuno dimentichi.

Un pattern che regge in produzione

Genera una chiave quando l'utente avvia l'azione (per esempio quando clicca Pay), poi salvala con il tuo record locale.

Ad ogni tentativo:

  • Invia la stessa chiave.
  • Salva il risultato finale ricevuto (risposta di successo, codice di errore, ID di addebito).
  • Se hai già un esito registrato, restituisci quell'esito invece di ripetere l'azione.

Se sei il "provider" per chiamate interne, applica lo stesso comportamento lato server.

Circuit breaker: smetti di chiamare l'API quando fallisce

Proteggi i tuoi workflow principali
Crea un flusso di checkout o registrazione che resti reattivo quando i provider rallentano.

Un circuit breaker è un interruttore di sicurezza. Quando un servizio esterno inizia a fallire, smetti di chiamarlo per un breve periodo invece di accumulare richieste che probabilmente andranno in timeout.

I circuit breaker hanno solitamente tre stati:

  • Closed: le richieste scorrono normalmente.
  • Open: le chiamate sono bloccate per una finestra di cooldown.
  • Half-open: dopo il cooldown, un piccolo numero di chiamate di prova verifica se il servizio è recuperato.

Quando il breaker è aperto, la tua app dovrebbe comportarsi in modo prevedibile. Se un'API di validazione indirizzi è giù durante la registrazione, accetta l'indirizzo e segnalo per una revisione successiva. Se un controllo rischio pagamento è giù, metti l'ordine in coda per revisione manuale o disabilita temporaneamente quell'opzione e spiegalo.

Scegli soglie che combacino con l'impatto sull'utente:

  • errori consecutivi (per esempio 5 fallimenti di seguito)
  • alta percentuale di errori in una finestra breve
  • molte risposte lente (timeout)
  • codici di stato specifici (come ripetuti 503)

Mantieni i cooldown brevi (secondi fino a un minuto) e limita le probe in half-open. L'obiettivo è proteggere prima i workflow core, poi recuperare rapidamente.

Fallback e code: mantieni l'app utilizzabile

Quando un'API esterna è lenta o giù, l'obiettivo è far muovere l'utente. Questo significa avere un Piano B onesto su ciò che è successo.

Fallback: scegli un'esperienza "abbastanza buona"

Un fallback è ciò che fa la tua app quando l'API non risponde in tempo. Le opzioni includono usare dati in cache, passare a una modalità degradate (nascondere widget non essenziali, disabilitare azioni opzionali), chiedere input all'utente invece di chiamare l'API (inserimento manuale dell'indirizzo), o mostrare un messaggio chiaro con il passo successivo.

Sii onesto: non dire che qualcosa è stato completato se non lo è stato.

Code: fallo dopo, quando il "ora" non è necessario

Se il lavoro non deve terminare dentro la richiesta utente, spostalo in una coda e rispondi velocemente. Casi comuni: invio email, sincronizzazione con CRM, generazione report e invio di eventi analytics.

Fallisci velocemente per azioni core. Se un'API non è necessaria per completare il checkout (o la creazione account), non bloccare la richiesta. Accetta l'ordine, metti la chiamata esterna in coda e riconcilia dopo. Se l'API è richiesta (per esempio autorizzazione pagamento), fallisci rapidamente con un messaggio chiaro e non far aspettare l'utente.

Quello che l'utente vede dovrebbe combaciare con quello che succede dietro le quinte: uno stato chiaro (completato, in sospeso, fallito), una promessa che puoi mantenere (ricevuta ora, conferma dopo), un modo per ritentare e un record visibile nell'UI (registro attività, badge "in sospeso").

Limiti di rate e carico: evita guasti auto-inflitti

I rate limit sono il modo del provider per dire: "Puoi chiamarci, ma non troppo spesso." Li raggiungerai prima di quanto pensi: picchi di traffico, job in background che partono insieme o un bug che loopa sugli errori.

Inizia controllando quante richieste generi. Raggruppa quando possibile, metti in cache le risposte anche per 30-60 secondi quando è sicuro, e applica throttling lato client così la tua app non burst più velocemente di quanto il provider permetta.

Quando ricevi un 429 Too Many Requests, trattalo come un segnale per rallentare.

  • Rispetta Retry-After quando viene fornito.
  • Aggiungi jitter così molti worker non ritentano nello stesso momento.
  • Limita i retry per i 429 per evitare loop infiniti.
  • Fai backoff più aggressivo su 429 ripetuti.
  • Registralo come metrica così noti i pattern prima degli utenti.

Limita anche la concorrenza. Un singolo workflow (come sincronizzare contatti) non dovrebbe consumare tutti gli slot worker e soffocare flussi critici come login o checkout. Pool separati o limiti per funzionalità aiutano.

Passo dopo passo: una ricetta di integrazione sicura di default

Possiedi il codice che generi
Genera l'integrazione, poi esporta il codice sorgente per rivederlo e personalizzarlo.

Ogni chiamata di terze parti ha bisogno di un piano per i guasti. Non serve la perfezione. Serve comportamento prevedibile quando il provider ha una giornata no.

1) Classifica la chiamata (necessaria subito vs può aspettare)

Decidi cosa succede se la chiamata fallisce ora. Un calcolo tasse durante il checkout potrebbe essere must-have. Sincronizzare un contatto marketing può di solito aspettare. Questa scelta guida tutto il resto.

2) Imposta timeout e budget di retry

Scegli timeout per tipo di chiamata e mantienili coerenti. Poi imposta un budget di retry così non continui a picchiare un'API lenta.

  • Must-have, utente in attesa: timeout breve, 0-1 retry.
  • Può aspettare, job in background: timeout più lungo, qualche retry con backoff.
  • Mai ritentare per sempre: limita il tempo totale speso per task.

3) Rendi i retry sicuri con idempotenza e tracciamento

Se una richiesta può creare qualcosa o addebitare soldi, aggiungi chiavi di idempotenza e salva un record della richiesta. Se una richiesta di pagamento va in timeout, un retry non dovrebbe doppiare l'addebito. Il tracciamento aiuta anche il supporto a rispondere: "È passato?"

4) Aggiungi un circuit breaker e un fallback

Quando gli errori aumentano, smetti di chiamare il provider per un breve periodo. Per le chiamate must-have, mostra un percorso "Riprova" chiaro. Per quelle che possono aspettare, metti il lavoro in coda e processalo dopo.

5) Monitora l'essenziale

Traccia latenza, tasso di errori ed eventi di apertura/chiusura del breaker. Allerta sui cambiamenti sostenuti, non sui singoli blip.

Errori comuni che trasformano un piccolo problema in downtime

La maggior parte degli outage API non parte grande. Diventa grande perché la tua app reagisce nel modo peggiore: aspetta troppo, ritenta troppo aggressivamente e occupa gli stessi worker che mantengono tutto il resto in funzione.

Questi pattern causano cascade:

  • Ritentare ogni fallimento, inclusi problemi 4xx come richieste invalide, auth scaduta o permessi mancanti.
  • Impostare timeout molto lunghi "per sicurezza", che consumano silenziosamente thread, connessioni DB o runner di job finché non esaurisci la capacità.
  • Ritentare azioni di create senza chiavi di idempotenza, portando a doppie fatturazioni, spedizioni duplicate o record ripetuti.
  • Circuit breaker mal configurati che non si riprendono mai o fluttuano aprendo e chiudendo.
  • Trattare outage parziali come failure totali invece di degradare solo la feature interessata.

Piccole correzioni prevengono grandi outage: ritenta solo errori probabilmente temporanei (timeout, alcuni 429, alcuni 5xx) e limita i tentativi con backoff e jitter; mantieni timeout brevi e intenzionali; richiedi idempotenza per ogni operazione che crea o addebita; e progetta per outage parziali.

Checklist rapida prima del rilascio

Rilascia funzionalità resilienti più velocemente
Crea, distribuisci e ospita la tua app con pattern di affidabilità integrati fin dall'inizio.

Prima di mettere un'integrazione in produzione, fai una passata veloce con mentalità da guasto. Se non riesci a rispondere "sì" a un punto, considera un blocco al rilascio per i workflow core come registrazione, checkout o invio di messaggi.

  • I limiti di tempo sono espliciti (connect timeout e read/response timeout).
  • I retry sono limitati (budget piccolo, backoff, jitter e cap sul tempo totale).
  • I retry sono sicuri per azioni reali (chiavi di idempotenza o chiare verifiche di deduplica).
  • C'è un breaker e un piano B (fallback, modalità degradata o coda).
  • Vedi i problemi presto (latency, tasso di errori e salute delle dipendenze per provider e endpoint).

Se un provider di pagamenti inizia a andare in timeout, il comportamento giusto è "il checkout si carica comunque, l'utente vede un messaggio chiaro e non resti appeso per sempre", non "tutto resta bloccato fino al timeout".

Esempio: proteggere il checkout quando un provider è instabile

Immagina un checkout che chiama tre servizi: un'API di pagamento per addebitare la carta, un'API tasse per calcolare l'imposta e un'API email per inviare la ricevuta.

La chiamata al pagamento è l'unica che deve essere sincrona. Problemi nella tassa o nell'email non dovrebbero bloccare l'acquisto.

Quando l'API tasse è lenta

Supponiamo che l'API tasse a volte impieghi 8-15 secondi. Se il checkout aspetta, gli utenti abbandonano il carrello e la tua app occupa worker.

Un flusso più sicuro:

  • Imposta un timeout netto (per esempio 800ms-2s) e fallisci velocemente.
  • Ritenta al massimo una volta, solo se è sicuro, con jitter.
  • Se scatta il timeout, usa una tariffa in cache o l'ultima tabella conosciuta per la regione dell'acquirente.
  • Se non puoi usare tariffe in cache per motivi legali, marca l'ordine come "tassa in sospeso" e metti in coda una ricalcolazione.

Risultato: meno carrelli abbandonati e meno ordini bloccati quando il provider tasse è lento.

Quando l'API email è giù

L'email di ricevuta è importante, ma non dovrebbe mai bloccare la cattura del pagamento. Se l'API email fallisce, il circuit breaker dovrebbe aprirsi dopo pochi fallimenti rapidi e bloccare le chiamate per una finestra di cooldown.

Invece di inviare l'email inline, metti un job "invia ricevuta" in coda con una chiave di idempotenza (per esempio order_id + email_type). Se il provider è giù, la coda ritenta in background e il cliente vede comunque un acquisto riuscito.

Risultato: meno ticket di supporto per conferme mancanti e niente perdita di fatturato perché il checkout falliva per ragioni non legate al pagamento.

Prossimi passi: applica questo in tutta l'app

Scegli un workflow che crea più problemi quando si rompe (checkout, registrazione, fatturazione) e rendilo la tua integrazione di riferimento. Poi copia gli stessi default ovunque.

Un ordine semplice di rollout:

  • Imposta timeout e fallisci velocemente con un errore chiaro.
  • Aggiungi retry con backoff, ma solo per errori retryabili.
  • Aggiungi idempotenza così i retry non raddoppiano addebiti o creazioni.
  • Aggiungi circuit breaker così un provider cattivo non può bloccare il tuo workflow core.

Documenta i tuoi default e mantienili noiosi: un connect timeout, un request timeout, conteggio massimo di retry, intervallo di backoff, cooldown del breaker e le regole su cosa è retryabile.

Esegui un drill di guasto prima di estendere al workflow successivo. Forza i timeout (o blocca il provider in un ambiente di test), poi conferma che l'utente vede un messaggio utile, i fallback funzionano e i retry in coda non si accumulano all'infinito.

Se stai costruendo nuovi prodotti velocemente, vale la pena trasformare questi default di affidabilità in un template riutilizzabile. Per i team che usano Koder.ai (koder.ai), questo spesso significa definire timeout, retry, idempotenza e regole del breaker una volta, poi applicare lo stesso pattern tra i nuovi servizi mentre generi e iteri.

Domande frequenti

Perché un'API di terze parti lenta può bloccare la mia app?

Imposta timeout espliciti di connessione e lettura per ogni chiamata. Scegli un limite breve per i flussi rivolti agli utenti, poi restituisci un risultato chiaro o sposta il lavoro non essenziale in una coda.

Quali errori API dovrei riprovare?

Riprova in caso di timeout, reset della connessione e alcuni errori 5xx, perché potrebbero risolversi rapidamente. Non riprovare richieste non valide, credenziali errate, errori di autorizzazione o endpoint inesistenti.

Quante volte dovrei riprovare una richiesta API?

Mantieni basso il numero di tentativi, di solito da uno a tre tentativi totali. Usa un backoff esponenziale con jitter casuale e limita il tempo totale dedicato ai tentativi.

Come posso evitare addebiti duplicati quando una richiesta di pagamento va in timeout?

Usa una chiave di idempotenza per le azioni che creano record, inviano messaggi, emettono rimborsi o addebitano denaro. Riutilizza la stessa chiave per ogni tentativo generato da una singola azione dell'utente.

Cosa fa un circuit breaker?

Un circuit breaker interrompe temporaneamente le chiamate dopo errori o timeout ripetuti. Protegge i tuoi worker e permette al fornitore di riprendersi prima di inviare di nuovo un numero limitato di richieste di prova.

Cosa dovrei spostare in una coda in background?

Mantieni reattivi checkout e accesso isolando le chiamate facoltative. Metti in coda le e-mail con le ricevute, gli aggiornamenti del CRM e le analisi invece di attenderli durante la richiesta dell'utente.

Come dovrebbe gestire la mia app una risposta 429 per limite di frequenza?

Rispetta il valore Retry-After del fornitore, se presente, riduci la concorrenza e aggiungi jitter prima di riprovare. Memorizza nella cache o raggruppa le letture sicure affinché la tua app effettui meno chiamate.

Cosa dovrebbero vedere gli utenti quando un servizio esterno non funziona?

Mostra lo stato reale: completato, in attesa o non riuscito. Indica agli utenti un passo pratico successivo, ad esempio riprovare il pagamento o controllare più tardi un ordine in attesa.

Cosa dovrei monitorare per le API di terze parti?

Monitora latenza, tasso di errori, tasso di timeout, risposte di limite di frequenza ed eventi del circuit breaker per ogni fornitore ed endpoint. Imposta avvisi per cambiamenti persistenti, così individuerai i problemi in anticipo.

Come posso testare un'integrazione API prima del rilascio?

Verifica timeout, risposte parziali, 429, errori 5xx e richieste di scrittura ripetute. Conferma che il fallback funzioni, che i job in coda restino entro limiti ragionevoli e che i tentativi non duplicano il lavoro.

Related posts