Polly .NET: guida pratica alla resilienza nel 2026
Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 25 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Ha guidato progetti enterprise, formato centinaia di sviluppatori e aiutato aziende di ogni dimensione a semplificare la complessità trasformando il software in guadagni per il business.

Il tuo team ha aperto un post-mortem per la terza volta questo trimestre, e la causa, ancora una volta, è un servizio esterno che ha smesso di rispondere.

Quante ore di reperibilità, quante scuse ai clienti, quanti sprint bruciati per rincorrere lo stesso problema con un nome diverso?

Il problema non è la competenza del tuo team.

Il problema è il "codice ottimista": quello scritto assumendo che i servizi esterni rispondano sempre, senza un piano per quando smettono di farlo.

In 25 anni ho visto lo stesso incidente ripetersi con nomi diversi, in aziende diverse.

Prendi Marco, che guida il team tecnico di un e-commerce di fascia media.

Una notte il gateway di pagamento ha smesso di rispondere, e con lui è andato giù anche il catalogo prodotti, che con i pagamenti non c'entrava nulla.

Il suo Direttore Generale lo ha saputo da un cliente su "X" (ex Twitter) prima che dal team stesso.

Devo dirti una cosa: non è colpa del tuo team.

Le reti distribuite falliscono, prima o poi: è statistica, non sfortuna.

Anche un team .NET che continua a subire incidenti di rete può costruire pipeline di resilienza corrette in poche settimane, lavorando sul codice già in produzione, senza fermare i rilasci.

Ecco perché qui non troverai frammenti di codice da copiare.

Invecchiano in fretta, e nessun team ha il tempo di riscrivere una guida ogni volta che cambia una versione.

Troverai invece il modo di ragionare su Polly, la libreria di resilienza per .NET, che il tuo team può applicare a prescindere dalla versione che sta usando oggi o userà tra due anni.

Se il tuo team sviluppa microservizi, applicazioni che chiamano API esterne o agenti AI che dipendono da un modello linguistico, questo articolo ti aiuta a capire cosa dovrebbe già saper configurare, e perché.

Cos'è Polly e perché ogni applicazione .NET in produzione ne ha bisogno

Polly .NET rende resilienti le chiamate ai servizi esterni

Polly è una libreria open source che raccoglie i pattern di resilienza più consolidati dell'ingegneria del software.

Nasce nel 2013 da Michael Wolfenden ed è oggi tra i pacchetti .NET Foundation più scaricati su NuGet, subito dopo quelli firmati Microsoft.

La domanda che conta non è "cos'è Polly", ma "perché il mio team ne ha bisogno".

La risposta è semplice: qualsiasi chiamata a un sistema esterno può fallire.

Il modo in cui il tuo team gestisce quel fallimento determina la qualità percepita del vostro sistema.

Un'applicazione che smette di funzionare perché il database risponde lento per 30 secondi non è un sistema elegante.

È un sistema fragile, indipendentemente da quanto sia pulito il codice al suo interno.

Il nemico, in questi casi, non ha un nome tecnico ufficiale, ma ne ha uno pratico: il codice ottimista, quello che un team scrive quando nessuno gli ha mai chiesto cosa succede se il servizio esterno non risponde.

I fallimenti che Polly gestisce rientrano in tre categorie distinte, e confonderle è il primo errore che porta a scelte sbagliate:

  • Errori transitori: timeout di rete, connessioni rifiutate, errori momentanei del server. Si risolvono da soli in pochi secondi o pochi tentativi.
  • Servizi in sovraccarico: limiti di traffico superati, latenze elevate, risposte degradate. Qui bisogna ridurre il traffico verso il servizio, non aumentarlo con altri tentativi.
  • Fallimenti prolungati: servizi fermi per minuti o ore. Il sistema deve degradare in modo elegante, senza trascinare giù le funzionalità che non dipendono da quel servizio.

Polly non impedisce che le cose falliscano.

Le gestisce quando falliscono, ed è questo che tiene in piedi un sistema quando una sua dipendenza smette di rispondere.

Nel mercato italiano questo si traduce in scenari concreti.

Un e-commerce continua a mostrare le pagine prodotto anche quando il gateway di pagamento è irraggiungibile per pochi minuti.

Un gestionale mantiene operativa la logistica mentre il fornitore del sistema ERP è in manutenzione.

Un assistente AI risponde con un messaggio ragionevole invece di esplodere quando il modello linguistico è sotto carico.

IHttpClientFactory: l'integrazione diretta con Polly v8

Chi nel tuo team ha usato Polly qualche anno fa conosce il vecchio approccio a regole separate, unite tra loro con un meccanismo macchinoso.

Funzionava, ma aveva limiti concreti: la composizione era complicata da leggere, l'integrazione con il resto dell'applicazione era manuale, e la raccolta dei dati di monitoraggio richiedeva lavoro aggiuntivo.

La versione attuale riscrive tutto introducendo la pipeline di resilienza come idea centrale.

Una pipeline è una sequenza ordinata di regole che avvolge un'operazione: ognuna intercetta la chiamata, applica la propria logica, e passa il controllo alla successiva.

Per iniziare serve un solo pacchetto NuGet, quello pensato per lavorare a fianco di HttpClientFactory.

Se servono anche scenari che non passano dalla rete, se ne aggiunge un secondo, pensato per costruire pipeline su misura.

La differenza con il passato si racconta più facilmente di quanto si legga nel codice.

Prima si costruiva ogni regola per conto suo, il retry da una parte, il circuit breaker dall'altra.

Poi si incollavano insieme con un meccanismo che invertiva l'ordine logico rispetto a come erano state scritte: la prima regola dichiarata finiva per essere quella più interna, non quella più esterna.

Confondeva chiunque nel team leggesse quel codice per la prima volta.

Oggi si costruisce invece un'unica sequenza dichiarata in ordine: si aggiunge il retry, poi il circuit breaker, poi il timeout, uno dopo l'altro.

L'ordine in cui vengono scritti è l'ordine in cui vengono eseguiti, dall'esterno verso l'interno.

Il salto tra le due versioni si riassume così:

AspettoApproccio precedentePipeline di resilienza (v8)
Composizione delle regoleOgni regola costruita a parte, poi unita con un meccanismo macchinosoSequenza unica dichiarata in ordine: retry, poi circuit breaker, poi timeout
Ordine di esecuzioneInvertito rispetto a come le regole erano scritte: la prima dichiarata diventava la più internaLineare: l'ordine di scrittura coincide con l'ordine di esecuzione, dall'esterno verso l'interno
Integrazione con l'applicazioneManualeDiretta con ASP.NET Core

La vera svolta, comunque, è l'integrazione diretta con il resto dell'applicazione ASP.NET Core, che vediamo tra poco.

Retry: come configurare i nuovi tentativi senza peggiorare il problema

Il retry è il pattern più semplice, e proprio per questo il più mal configurato.

La logica di base è ovvia: se un'operazione fallisce, riprova.

Il problema sta nei dettagli: quante volte, con quale intervallo, per quali errori, e cosa fare quando anche l'ultimo tentativo fallisce.

Il primo errore tipico è un intervallo fisso tra i tentativi.

Se cento client falliscono nello stesso istante, dopo un breve blackout del server, riprovano tutti insieme e generano un picco di traffico peggiore del problema originale.

La soluzione combina due ingredienti: un'attesa che si allunga a ogni nuovo tentativo, e una piccola variazione casuale, chiamata jitter, che impedisce a client diversi di ripresentarsi tutti insieme.

Si configurano diversi parametri:

  • Quanti tentativi concedere
  • Quanto aspettare al primo tentativo
  • Un tetto oltre il quale l'attesa non deve mai salire
  • Quali errori meritano davvero un secondo tentativo

Un timeout di rete o un servizio momentaneamente sovraccarico lo meritano.

Un errore che dipende da una richiesta scritta male, no: riprovare non lo risolve, spreca solo tempo, e in produzione tempo significa risorse pagate per niente.

Quando si chiama un servizio con un limite di traffico, molti fornitori indicano con precisione quanto aspettare prima di riprovare.

Ignorare quell'indicazione è controproducente: si consuma margine inutilmente e si rischia un blocco temporaneo.

La logica corretta è semplice da raccontare: se il servizio indica quanto aspettare, si aspetta esattamente quello, con un piccolo margine di sicurezza in più.

Se non lo indica, si ripiega sull'attesa crescente di cui sopra.

Una regola pratica: al massimo tre o quattro tentativi per operazioni che rispondono direttamente a un utente, dove ogni secondo conta.

Si può salire fino a dieci per lavori in background, dove la latenza pesa meno del risultato finale.

Tre tentativi, un'attesa che cresce, un margine di sicurezza: sono regole di partenza, non la verità assolute.

La configurazione giusta dipende dal comportamento dei servizi che il tuo team chiama ogni giorno, non da un valore preso da un post su internet.

Jitter, backoff, timeout per singolo tentativo: se questi concetti non ti erano familiari, non è colpa tua.

Sono dettagli che nessun tutorial online insegna davvero, ma che separano un team che scrive codice da uno che progetta sistemi affidabili.

Il nostro Corso C# costruisce queste basi da zero, con lo stesso metodo che il tuo team può applicare subito.

Circuit breaker: le tre fasi che bloccano i fallimenti a cascata

Il circuit breaker prende il nome dal dispositivo elettrico: quando la corrente supera una soglia, il circuito si apre e interrompe il flusso.

Nel software, quando un servizio supera una soglia di fallimento, il circuit breaker blocca le chiamate verso quel servizio, proteggendo sia chi chiama sia chi viene chiamato.

Il problema che risolve è sottile.

Senza circuit breaker, un retry risponde ai fallimenti aumentando il traffico verso un servizio già in difficoltà.

Il servizio che fatica a rispondere viene sommerso da altri tentativi, e la situazione peggiora invece di stabilizzarsi.

Il circuit breaker attraversa tre fasi.

Nella prima, quella normale, le chiamate passano e il sistema si limita a osservare il tasso di fallimento.

Quando quel tasso supera la soglia scatta la seconda fase: le chiamate vengono bloccate subito, senza nemmeno tentare quella reale, lasciando respirare il servizio in difficoltà.

Dopo una pausa arriva la terza fase, quella di prova: un numero limitato di chiamate viene ammesso per capire se il servizio è tornato operativo.

Se va bene, si torna alla normalità.

Se va male, si ricomincia la pausa.

Si definisce una soglia di fallimento oltre la quale il circuito si apre, per esempio metà delle chiamate andate storte.

Si definisce anche un numero minimo di richieste da osservare prima di fidarsi di quella soglia, così non scatta al primo intoppo su un servizio poco usato.

Si aggiunge quanto a lungo il circuito resta aperto prima di concedere un nuovo tentativo.

Quando il circuito è aperto, il codice applicativo riceve un avviso specifico al posto della risposta attesa, e deve gestirlo in modo esplicito.

Nella maggior parte dei casi non lo lascia risalire fino all'utente, ma lo intercetta e restituisce un'alternativa ragionevole, per esempio l'ultimo dato salvato in cache poco prima che il servizio smettesse di rispondere.

Un sistema senza circuit breaker non sta scalando: sta solo aspettando il momento in cui un servizio lento trascinerà giù anche quelli sani.

Ora è chiaro perché il codice ottimista crolla sotto pressione: mancano le fasi intermedie, mancano le soglie, manca la disciplina di progettare per il fallimento invece che per il caso felice.

Ma è altrettanto importante essere chiari su cosa può dare, e cosa no, un articolo come questo.

Leggerlo dà al tuo team il modo di ragionare.

Non dà le ore passate a sbagliare in produzione prima di calibrarlo sul traffico reale della tua azienda.

Non è una questione di quanti anni di .NET ha il tuo team.

Esperienza e metodo non sono la stessa cosa: si può avere dieci anni di esperienza e configurare comunque una pipeline fragile, se nessuno ha mai insegnato a progettare per il fallimento invece che per il caso felice.

Ecco cosa racconta, in concreto, un Direttore IT di un'azienda e-commerce italiana di fascia media, dopo aver riprogettato con il proprio team le pipeline di resilienza:

"Prima ogni incidente di rete ci costava mezza giornata di gestione dell'emergenza e un cliente arrabbiato.

Ora il sistema degrada, non crolla, e il team lo gestisce da solo, senza aspettare che torni il senior che aveva scritto quel pezzo di codice."

È il tipo di risultato che nasce da un lavoro più mirato di quanto un articolo da solo possa offrire: nel percorso con un tutor analizziamo insieme le pipeline di resilienza che il tuo team ha già in produzione, non quelle da manuale.

Il beneficio è diretto: il team smette di scoprire un problema di configurazione durante un incidente, e inizia a scoprirlo durante una revisione tranquilla del codice.

Il beneficio più profondo è organizzativo: il team smette di dipendere da un solo senior che "sa come funziona", e costruisce un metodo che chiunque può applicare.

Il costo di arrivarci è una frazione di quello che perde un'azienda in un solo incidente di produzione non gestito.

Timeout: quanto tempo può permettersi di aspettare il tuo sistema prima di arrendersi

Il timeout limita attese e blocchi nelle chiamate esterne

Il timeout è il meccanismo di difesa più semplice, e quello più spesso trascurato.

Il valore predefinito in .NET è 100 secondi: in produzione, una chiamata che non risponde tiene occupato un thread, e una connessione, per quasi due minuti.

Con Polly si definiscono due livelli distinti: uno per il singolo tentativo, uno per l'intera operazione, retry compresi.

La distinzione è fondamentale, e confonderla è un errore che ho visto ripetersi in quasi ogni azienda che ho seguito.

Il timeout complessivo va registrato prima del retry nella sequenza, quello per singolo tentativo dopo.

Così il primo avvolge l'intera operazione, comprese tutte le attese tra un tentativo e l'altro, mentre il secondo si azzera a ogni nuovo tentativo e non si accumula con i precedenti.

Per calibrare i valori giusti, una formula pratica: si misura il tempo di risposta abituale del servizio nei momenti di picco, e lo si moltiplica per due o tre per ottenere il timeout del singolo tentativo.

Per il timeout complessivo, si somma quel valore per ogni tentativo previsto, si aggiungono le attese tra un tentativo e l'altro, e si lascia un margine.

Per i modelli linguistici la situazione cambia: una risposta a un prompt complesso può richiedere anche un minuto.

Qui il timeout deve essere abbastanza generoso da coprire la latenza reale, senza tagliare a metà una risposta legittima.

100 secondi di default in .NET.

Se non lo sapevi, quante altre configurazioni implicite non conosci?

.NET è pieno di comportamenti impliciti che nessuno segnala finché non causano un incidente in produzione.

Nel Corso C# il tuo team impara a riconoscerli prima che diventino un problema, non dopo.

Bulkhead e limitazione del traffico: proteggere le risorse quando un servizio rallenta tutti gli altri

Immagina un servizio lento che riceve duecento richieste nello stesso istante.

Senza un limite, ognuna di quelle richieste tiene occupata una risorsa, un processo, una connessione, un blocco di memoria, finché non ottiene risposta.

Il resto dell'applicazione, quello che non ha nulla a che fare con il servizio lento, finisce comunque incolonnato dietro un imbuto che nessuno ha disegnato apposta.

Il bulkhead, che prende il nome dalle paratie stagne delle navi, isola le risorse: assegna un tetto massimo di richieste parallele verso un singolo servizio, con una coda per chi arriva quando quel tetto è già raggiunto.

Quando anche la coda si riempie, la scelta più comune è rifiutare la nuova richiesta in arrivo, lasciando la coda invariata, e registrare l'evento per capire quanta pressione sta subendo il sistema.

Polly mette a disposizione anche una strategia per limitare il traffico generato verso l'esterno, utile quando è l'azienda a dover rispettare un limite imposto da un fornitore, non il contrario.

La differenza è sottile ma importante:

AspettoBulkheadLimitazione del traffico (rate limiting)
Cosa proteggeLe risorse interne (connessioni, memoria, processi)Il rapporto con il fornitore esterno
Come agisceImpone un tetto massimo di richieste parallele verso un servizio, con una coda per chi arriva dopoContiene il traffico generato verso l'esterno entro i limiti imposti dal fornitore
Quando serveQuando un servizio lento rischia di occupare tutte le risorse dell'applicazioneQuando è la tua azienda a dover rispettare un limite imposto da terzi

Fallback: l'ultima risposta quando tutto il resto ha smesso di funzionare

Un cliente vede "dati aggiornati a 5 minuti fa".

Un altro vede una pagina di errore.

Dietro le quinte il problema era identico: un servizio che non rispondeva.

Il fallback decide quale dei due clienti si trova davanti la tua azienda.

È l'ultima linea di difesa: entra in gioco quando retry, circuit breaker e timeout non sono riusciti a ottenere una risposta utile.

Definisce cosa restituire al loro posto, un valore di default, l'ultima copia salvata in cache, una risposta parziale, invece di lasciar risalire l'errore fino a chi sta aspettando.

In produzione, il fallback si combina spesso con una cache: quando il servizio primario non risponde, si servono i dati dall'ultima copia salvata.

Se anche la cache è vuota, per un avvio a freddo o un aggiornamento recente, il sistema risponde comunque con un valore ragionevole invece di propagare un errore.

Non è la soluzione perfetta.

È quella che tiene in piedi l'esperienza del cliente mentre il problema vero si risolve altrove.

Dal fallback al timeout finale: l'errore d'ordine che vanifica tutto il resto

La trappola più frequente in una pipeline con più regole è mettere il circuit breaker prima del retry.

Con questo ordine sbagliato, il circuit breaker vede solo il risultato finale di tutti i tentativi, non ogni singolo tentativo, e perde la capacità di aprirsi in fretta durante una serie di fallimenti.

Un'altra trappola comune: dimenticare il timeout complessivo e tenere solo quello per il singolo tentativo.

Con tre tentativi, un timeout di dieci secondi ciascuno e attese crescenti tra un tentativo e l'altro, l'operazione intera può superare i cinquanta secondi.

Su un endpoint che risponde a un cliente in attesa, è quasi sempre inaccettabile.

L'ordine giusto segue invece una logica precisa, dall'esterno verso l'interno:

  1. Fallback: intercetta ciò che nessun altro livello ha gestito
  2. Timeout complessivo: limita il tempo massimo dell'intera operazione
  3. Retry: riprova quando ha senso
  4. Circuit breaker: blocca le chiamate quando il servizio è in difficoltà
  5. Timeout per il singolo tentativo: il più interno di tutti
Con questo ordine, ogni regola protegge quella successiva, senza sovrapporsi alle sue responsabilità.

È una sequenza che, una volta capita, non si scorda più: la prima regola registrata è la più esterna, l'ultima a vedere il risultato finale.

Polly con HttpClientFactory in ASP.NET Core: il punto d'ingresso di quasi ogni applicazione moderna

IHttpClientFactory integra Polly nelle chiamate HTTP .NET

Vi state chiedendo se tutta questa resilienza si applichi solo alle chiamate HTTP?

Quasi tutte le applicazioni .NET moderne passano da lì, ed è per questo che l'integrazione con HttpClientFactory è il modo consigliato per usarla.

HttpClientFactory gestisce il ciclo di vita dei client HTTP evitando i problemi di esaurimento delle connessioni tipici della creazione manuale, e la resilienza si aggancia sopra senza toccare il resto del codice.

Il pacchetto dedicato offre due strade.

La prima è una configurazione già pronta, con valori di default ragionevoli: perfetta per iniziare subito, personalizzando solo ciò che serve davvero, come il numero di tentativi o la durata della pausa del circuit breaker.

La seconda dà controllo completo sulla costruzione della pipeline, utile quando le esigenze del servizio sono troppo specifiche per una configurazione standard.

Non solo chiamate HTTP: database, code, file system

Polly non serve solo per le chiamate di rete.

Qualsiasi operazione che può fallire in modo transitorio è candidata alla resilienza, e i database ne sono l'esempio più comune: blocchi contemporanei, timeout di connessione, brevi indisponibilità del server capitano più spesso di quanto si pensi.

In questi casi, il retry intercetta solo gli errori davvero transitori, riconoscibili dal loro codice specifico, e lascia propagare tutti gli altri, quelli che un nuovo tentativo non risolverebbe comunque.

Metriche di Polly: i segnali più precoci di qualcosa che si rompe

Il tuo team ha mai scoperto un problema di configurazione solo perché un cliente ha scritto per lamentarsi, invece che da un avviso automatico?

È il segnale più chiaro che mancano le metriche giuste.

Una pipeline di resilienza senza questi dati è come un circuit breaker senza una spia che segnali quando si apre: non si sa quando succede, non si sa quanto spesso, e lo si scopre solo quando è già un problema visibile ai clienti.

Polly si integra in modo nativo con il sistema di metriche di .NET e con OpenTelemetry.

Basta agganciare la sorgente dedicata nella configurazione, insieme alla strumentazione standard per le chiamate HTTP, e i dati iniziano a fluire automaticamente verso lo strumento scelto, che sia Prometheus, la console o un altro sistema di monitoraggio.

Le informazioni che se ne ricavano coprono tre aspetti.

Quanti eventi di resilienza sono scattati e di che tipo: un retry, un cambio di stato del circuit breaker, un timeout.

Quanto dura ogni esecuzione, utile per capire dove si nasconde la latenza reale.

E quanti tentativi sono serviti per completare un'operazione.

I pannelli che contano davvero

Con Prometheus e Grafana, questi dati diventano pannelli operativi che il team può controllare senza aprire il codice.

I quattro più utili sono questi:

  • Il tasso di nuovi tentativi per singolo servizio: un aumento improvviso segnala un problema esterno in corso
  • Lo stato del circuit breaker: indica quali servizi sono in difficoltà proprio ora
  • La percentuale di timeout: dice se i valori sono calibrati in modo troppo aggressivo
  • Il tasso di fallback: misura quanto spesso il sistema sta servendo dati degradati invece di quelli reali
Un consiglio maturato su più progetti: si imposta un avviso quando un circuit breaker si apre o quando i tentativi superano il 20% in una finestra di 5 minuti.

Sono i segnali più precoci di un problema nelle dipendenze esterne, molto prima che se ne accorga un cliente.

Le metriche giuste si progettano, non si scoprono dopo un incidente.

Sapere cosa misurare, e perché, nasce dalla stessa disciplina che sta dietro ogni riga di codice ben scritta.

È quello che alleniamo nel Corso C#: non solo sintassi, ma il modo di ragionare che porta un team a costruire sistemi osservabili fin dal primo commit.

Polly per gli agenti AI: quando il costo di un errore non è solo il tempo perso

Se il tuo team costruisce agenti AI con Semantic Kernel o sistemi che recuperano informazioni da una base di conoscenza, la resilienza cambia carattere.

Provider come OpenAI e Azure OpenAI impongono limiti aggressivi su finestre di 60 secondi, con risposte che possono richiedere da mezzo secondo a un minuto intero a seconda della complessità della richiesta.

Gli aggiornamenti dei modelli, poi, causano brevi indisponibilità che con un servizio tradizionale non si vedrebbero quasi mai.

Quando si superano quei limiti, il fornitore risponde con un errore preciso e, quasi sempre, un'indicazione di quanti secondi aspettare prima di riprovare.

Ignorarla e usare un'attesa generica è un errore comune quanto costoso: se viene chiesto di aspettare 20 secondi e si riprova dopo 4, si spreca il tentativo, si ottiene un altro rifiuto, e si brucia ulteriore margine.

La differenza rispetto a un servizio tradizionale si vede su tre fronti:

AspettoServizio esterno tradizionaleProvider LLM (OpenAI, Azure OpenAI)
Limiti di trafficoGeneralmente stabili nel tempoAggressivi, calcolati su finestre di 60 secondi
Tempo di rispostaPrevedibileDa mezzo secondo a un minuto intero, a seconda della complessità della richiesta
Costo di un retry sbagliatoLatenza persaLatenza persa più token pagati per una risposta che non arriverà mai

La strategia corretta rispetta quel valore, con un piccolo margine di sicurezza in più, e ripiega su un'attesa crescente solo quando manca un'indicazione esplicita.

Il circuit breaker, in questo scenario, va tarato in modo più prudente rispetto a un servizio qualsiasi.

Con un modello in difficoltà, ogni tentativo in più non è solo latenza persa.

È un costo reale in token pagati per una risposta che non arriverà mai, una voce di spesa che qualcuno in azienda dovrà giustificare a fine mese.

Per le applicazioni che non possono permettersi un'interruzione, un pattern avanzato aggiunge un fallback su un fornitore alternativo.

Se il servizio primario è al limite, si passa temporaneamente a un secondo provider.

Restano comunque timeout, retry e circuit breaker dedicati al primo, così il passaggio scatta solo quando è davvero necessario.

Configurare Polly correttamente fa la differenza tra un agente che degrada con eleganza e uno che bombarda il fornitore generando costi inaspettati.

Lo stesso principio, applicato su scala più ampia, vale per qualsiasi architettura distribuita: le applicazioni su più microservizi hanno di solito diversi livelli di chiamate esterne, e ognuno merita una pipeline propria.

Un servizio di pagamento merita tempi più larghi e un circuit breaker più prudente di un servizio di notifiche non critico.

Configurare Polly in produzione: cosa cambia rispetto allo sviluppo

In fase di sviluppo si vuole vedere subito l'effetto di un errore: log dettagliati, attese quasi nulle, un solo tentativo prima di arrendersi.

In produzione serve l'esatto opposto: log essenziali, soglie calibrate sul traffico reale, metriche raccolte in continuo.

La stessa pipeline può leggere questi valori dal resto dell'applicazione e adattarsi da sola all'ambiente in cui gira, senza bisogno di due versioni parallele da mantenere.

Conviene tenere questi valori fuori dal codice, in un file di configurazione o in un servizio dedicato come Azure App Configuration.

Quando in produzione emerge un problema, poter allungare la pausa di un circuit breaker senza un nuovo rilascio fa la differenza tra 5 e 30 minuti di interruzione.

È anche la differenza tra un incidente minore e uno che finisce sul tavolo del Direttore Generale.

Verificare che la resilienza funzioni, prima che lo scopra un cliente

Testare il circuit breaker evita guasti in produzione

"Abbiamo configurato il retry, funzionerà" è una frase che sento spesso da team con anni di esperienza, e quasi sempre precede un incidente.

Senza una verifica reale non si sa se il circuit breaker si apre davvero, se i tentativi rispettano l'attesa crescente configurata, o se il fallback restituisce i dati giusti quando tutto il resto fallisce.

Il problema, storicamente, era il tempo: verificare una pipeline con più tentativi e attese crescenti poteva richiedere minuti solo per controllare i casi di errore.

.NET offre da qualche versione un modo per simulare il tempo, avanzandolo a piacere invece di aspettarlo davvero.

Si possono far scattare tre tentativi con le loro attese crescenti in una manciata di millisecondi di esecuzione reale, verificando che i tempi calcolati siano esattamente quelli previsti.

Con lo stesso principio si possono simulare le risposte di un servizio esterno, per esempio facendo fallire i primi tentativi e rispondere correttamente solo dal successivo.

Si verifica così che il circuit breaker si apra dopo la soglia configurata e si richiuda dopo la pausa prevista, senza mai aspettare un secondo reale di orologio.

Vale la pena inserire questi controlli nella pipeline di integrazione continua.

Non sono i più veloci in assoluto, ma restano rapidi abbastanza da non rallentarla, e sapere che il circuit breaker si comporta come previsto è una delle poche garanzie solide che un'azienda può avere su un sistema distribuito.

Ogni servizio esterno chiamato senza una pipeline di resilienza è un incidente che aspetta solo il momento sbagliato per succedere.

Non è una questione di se, è una questione di quando: e quando succede in produzione, il conto lo paga il team che sarà in reperibilità quella notte, e il cliente che se ne accorge prima di voi.

Nel percorso, un tutor segue direttamente ogni team che accettiamo: non può farlo con un numero illimitato di aziende in parallelo, quindi i posti sono limitati per costruzione, non per marketing.

Il percorso è su candidatura: valutiamo il contesto del tuo team prima di proporre qualsiasi cosa, non è pensato per chi cerca una scorciatoia, ma per chi vuole costruire un metodo che resti anche quando cambiano le persone in squadra.

La domanda non è se il prossimo timeout coglierà di sorpresa il tuo team.

È se, quando succede, il vostro sistema degraderà con eleganza o trascinerà giù tutto il resto.

Marco non ha più visto un errore di rete trasformarsi in un'interruzione totale nei mesi successivi.

Non perché il suo team ha imparato più tecnologie.

Perché ha imparato a progettare per il fallimento invece che sperare che non arrivasse mai.

Quando vuoi lo stesso risultato per il tuo team, parti da qui.

C'è un'unica differenza reale tra il team di Marco oggi e il team di Marco di un anno fa: non ha imparato più .NET.

Ha smesso di progettare pensando che tutto vada sempre bene.

Il tuo team può fare lo stesso, ma non improvvisando due ore un sabato su un tutorial.

Il Corso C# esiste per questo: costruire, riga per riga, la disciplina di chi progetta per il fallimento invece di sperare che non arrivi mai.

I team che smettono di progettare solo per lo scenario perfetto, lo fanno per scelta, non perché qualcosa li ha già costretti a farlo.

Decidi tu da che parte vuoi che stia il tuo.

Domande frequenti

Polly è una libreria open source per .NET che implementa pattern di resilienza come retry, circuit breaker, timeout, bulkhead e fallback. Serve a rendere le applicazioni più robuste di fronte a errori transitori, servizi esterni non disponibili e condizioni di sovraccarico, evitando che un singolo fallimento si propaghi a cascata.

Polly v8 introduce la Resilience Pipeline come costrutto centrale al posto delle singole Policy. La nuova API è fortemente integrata con Microsoft.Extensions.Http.Resilience e con l'ecosistema .NET moderno. Il vecchio approccio basato su Policy.Handle e WrapAsync è ancora supportato ma deprecato. Con v8 si definiscono pipeline usando ResiliencePipelineBuilder e si configura tramite AddResilienceHandler in ASP.NET Core.

Con Polly v8 si usa AddRetry() sulla ResiliencePipelineBuilder. Puoi configurare MaxRetryAttempts, Delay, BackoffType (costante, lineare o esponenziale) e ShouldHandle per specificare quali eccezioni o risultati causano un retry. Il jitter automatico previene il problema del thundering herd quando molti client riprovano contemporaneamente.

Il circuit breaker è un pattern che interrompe automaticamente le chiamate verso un servizio che sta fallendo, evitando di sovraccaricare un sistema già in difficoltà. Ha tre stati: Closed (tutto normale), Open (le chiamate vengono bloccate senza nemmeno tentare) e Half-Open (si ammette un numero limitato di tentativi per verificare il ripristino). Si usa quando si chiama un servizio esterno o un database che può andare in sovraccarico.

Con ASP.NET Core si registra la pipeline di resilienza usando AddResilienceHandler() nell'estensione di IHttpClientBuilder. Così ogni richiesta HTTP effettuata tramite quel client beneficia automaticamente di retry, circuit breaker e timeout, senza dover gestire manualmente la pipeline nel codice applicativo.

In condizioni normali l'overhead di Polly è trascurabile: pochissimi microsecondi per richiesta. Il costo vero si paga solo quando si attivano i meccanismi di resilienza (retry aggiunge latenza, circuit breaker aperto accelera il fallimento). Configurato correttamente, Polly migliora le prestazioni percepite riducendo i timeout globali e le degradazioni a cascata.

Lascia i tuoi dati nel form qui sotto

Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 25 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Nel corso della sua carriera ha collaborato con realtà come Cotonella, Il Sole 24 Ore, FIAT e NATO, guidando team nello sviluppo di piattaforme scalabili e modernizzando ecosistemi legacy complessi.

Ha formato centinaia di sviluppatori e affiancato aziende di ogni dimensione nel trasformare il software in un vantaggio competitivo, riducendo il debito tecnico e portando risultati concreti in tempi misurabili.

Stai leggendo perché vuoi smettere di rattoppare software fragile.Scopri il metodo per progettare sistemi che reggono nel tempo.