Ci siamo già occupati in precedenza di Polly (https://github.com/App-vNext/Polly), una libreria open source estremamente versatile per realizzare applicazioni robuste in scenari service oriented. In contesti simili, infatti, dobbiamo sempre essere consci che le nostre richieste verso servizi esterni possono potenzialmente fallire, e pertanto dobbiamo mettere in atto contromisure affinché l'esperienza utente ne venga condizionata il meno possibile.
In particolare, negli script precedenti, abbiamo visto come implementare:
- una logica di Retry (https://www.aspitalia.com/script/1255/Impostare-Retry-Automatici-Chiamata-Servizio-ASP.NET-Core-MVC.aspx)
- il pattern Circuit Breaker (https://www.aspitalia.com/script/1256/Implementare-Pattern-Circuit-Breaker-ASP.NET-Core-MVC.aspx)
- la gestione di un Timeout (https://www.aspitalia.com/script/1257/Gestire-Timeout-Operazione-Polly-ASP.NET-Core-MVC.aspx)
Con questo script vogliamo chiudere il ciclo mostrando come sia possibile combinare queste funzionalità per gestire logiche molto complesse.
Come al solito, per poter usufruire di Polly, il primo passo è installare il package NuGet relativo:
install-package Polly
A questo punto, all'interno di Startup.cs, possiamo configurare le policy a nostro piacimento:
public void ConfigureServices(IServiceCollection services) { // ...altro codice qui... var retry = Policy<IEnumerable<Article>> .Handle<Exception>() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: r => TimeSpan.FromSeconds(r * 0.5)); var breaker = Policy<IEnumerable<Article>> .Handle<Exception>() .CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); var fallback = Policy<IEnumerable<Article>> .Handle<Exception>() .FallbackAsync(ct => new AlternateContentService().LoadArticlesAsync()); var policy = Policy.WrapAsync(fallback, breaker, retry); services.AddSingleton<Policy<IEnumerable<Article>>>(policy); }
Nell'esempio di codice in alto, abbiamo instanziato tre policy che gestiscono un servizio che ritornerà una lista di Article. Nello specifico:
- una policy di Retry, che riprova ad invocare il servizio fino a un massimo di 3 volte, incrementando di volta in volta il tempo di attesa;
- una policy di CircuiteBreaker, che dopo il fallimento dell'esecuzione, esclude la chiamata al servizio per i successivi 10 secondi;
- una policy di Fallback che, in caso di errore del servizio chiamato, utilizza un servizio alternativo (per esempio potrebbe essere un file di cache locale) per caricare i dati.
Queste tre policy possono essere combinate insieme attraverso il metodo WrapAsync, in modo da implementare la logica di esecuzione descritta dall'immagine in basso. Come possiamo notare, le varie policy vanno elencate a partire dalla più esterna fino alla più interna.

Si tratta di una logica parecchio complessa e assolutamente non banale da implementare a mano, ma che possiamo sfruttare all'interno di un controller in maniera assolutamente analoga agli esempi degli altri script, semplicemente referenziando la policy e utilizzandola per l'esecuzione di LoadArticlesAsync:
private IContentService _content; private Policy<IEnumerable<Article>> _articlePolicy; public HomeController(IContentService content, Policy<IEnumerable<Article>> articlePolicy) { _content = content; _articlePolicy = articlePolicy; } public async Task<IActionResult> Index() { IEnumerable<Article> articles = null; Stopwatch watch = Stopwatch.StartNew(); try { articles = await _articlePolicy.ExecuteAsync(_content.LoadArticlesAsync); } catch (Exception ex) { ViewBag.Message = "Articoli non disponibili al momento"; } watch.Stop(); ViewBag.Time = $"{watch.ElapsedMilliseconds} ms"; return View(articles); }
Il controller, infatti, accetta una Policy come parametro del costruttore, assieme a IContentService, e sfrutta il metodo ExecuteAsync per effettuarne un'invocazione controllata.
Se proviamo a eseguire il codice in allegato, in cui l'implementazione di IContentService fallisce sempre, noteremo che la prima esecuzione impiega circa 3 secondi. Questo è dovuto ai 3 retry (rispettivamente con attese di 500ms, 1000ms, 1500ms) sulla chiamata a LoadArticlesAsync.

A questo punto il CircuitBreaker si attiverà, escludendo ContentService dall'esecuzione per i successivi 10 secondi. Se proviamo infatti a effettuare un refresh immediato della pagina, vedremo come il caricamento sarà molto più veloce.

In entrambi i casi, al di là di un leggero delay, grazie alla funzionalità di Fallback, l'utente non riceverà mai un errore, ma semplicemente un messaggio che indica che i dati sono stati caricati da AlternateContentService.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Calcolare automaticamente un numero di versione di un repository in una GitHub Action
Effettuare un loop di una GitHub Action
Dichiarare una variabile in JavaScript con le parole chiave var, let e const
Offline first con Blazor e IndexedDB
Impostare il claim desiderato per il nome utente in ASP.NET Core con Microsoft Identity
Autenticazione con Minimal API di ASP.NET Core 6
Dichiarare una struct come record in C#
Integrare e trasformare dati con Azure Data Factory
Dependency injection con Minimal API di ASP.NET Core
Compilare un'applicazione .NET Core con una GitHub Action
Chiamare un endpoint ASP.NET Core protetto da Certificate Authentication
Utilizzare Front Door come CDN di contenuti statici
I più letti di oggi
- Produttività con ASP.NET Core 6
- Le novità di Angular 14
- Velocizzare l'installazione delle dipendenze in un workflow di GitHub
- Monitorare e prevenire problemi in produzione
- YARP: un reverse proxy in ASP.NET Core
- Blazor PWA e Offline-First
- GitHub Actions e Terraform: l'infrastruttura, dalla definizione al deploy
- DateOnly e TimeOnly in .NET: e io che ci faccio?
- Usare Docusaurus per creare un sito di documentazione