Il pattern Circuit Breaker è di fondamentale importanza in un'architettura service-oriented, perchè impedisce che il malfunzionamento di un servizio non si ripercuota a catena su tutti gli altri componenti che lo utilizzano.
Cerchiamo di comprenderne la logica guardando l'immagine in basso, e immaginiamo di avere un servizio che sia momentaneamente non disponibile.
Quando effettuiamo una chiamata tramite il circuit breaker, inizialmente in stato Closed, il servizio viene effettivamente invocato e, pertanto, solleverà un'eccezione - per esempio un errore HTTP 500.
Dopo un certo numero di errori consecutivi, il circuito passerà in stato Open. In questa condizione, la chiamata viene immediatamente bypassata, e ci verrà immediatamente sollevata un'eccezione di tipo CircuitBreakerException.
Il fatto che il circuito fallisca subito è fondamentale. Pensiamo infatti a cosa potrebbe accadere con una risorsa che vada in timeout: ogni utente dovrebbe attendere diversi secondi prima di vedersi restituire un messaggio di errore. In stato Open, invece, possiamo evitare completamente di chiamare il servizio in errore, magari indicando che solo una specifica parte della pagina non può essere popolata correttamente a causa di questo problema, ma comunque salvaguardando la user experience.
Una volta trascorso un certo lasso di tempo, il circuito si sposterà in stato Half-Closed: in questa modalità, proverà di nuovo a chiamare il servizio, ma se dovesse verificarsi ancora un errore, tornerà immediatamente in stato Open. Diversamente si riporterà in stato Closed.
La libreria Polly (https://github.com/App-vNext/Polly) che abbiamo iniziato a utilizzare nello script precedente, possiede già out-of-the-box un'implementazione di circuit breaker, che ci permette molto facilmente di integrare la logica che abbiamo visto in precedenza nella nostra applicazione.
Dopo aver installato il package NuGet, il primo passo è quello di registrare e configurare il Circuit Breaker nel metodo ConfigureServices della classe Startup.
public void ConfigureServices(IServiceCollection services) { // .. altri servizi qui .. var breaker = Policy .Handle<Exception>() .CircuitBreakerAsync(2, TimeSpan.FromSeconds(10)); services.AddSingleton<CircuitBreakerPolicy>(breaker); }
Nel nostro caso, il circuito si aprirà automaticamente dopo 2 exception di qualsiasi tipo, e rimarrà in questo stato per un TimeSpan di 10 secondi. E' assolutamente importante registrare il servizio come Singleton, perché l'istanza deve essere condivisa tra tutti i chiamanti e lo stato deve essere persistito tra le varie request.
A questo punto, nel controller, possiamo recuperare l'istanza di CircuitBreakerPolicy e utilizzarla per chiamare il servizio esterno.
public HomeController(IContentService content, CircuitBreakerPolicy breaker) { _content = content; _breaker = breaker; } public async Task<IActionResult> Index() { string[] articles; try { articles = await _breaker.ExecuteAsync( () => _content.LoadArticlesAsync()); } catch (Exception) { articles = new[] { "Articoli non disponibili al momento" }; } ViewBag.State = _breaker.CircuitState; return View(articles); }
La action Index, tramite il metodo ExecuteAsync del circuit breaker, può effettuare una chiamata protetta al metodo LoadArticlesAsync: quando questa fallisce, l'esito sarà memorizzato nella macchina a stati del circuito, che si comporterà secondo la logica che abbiamo descritto in precedenza.
Per capire il beneficio di questo pattern, possiamo provare a eseguire il codice allegato allo script, in cui LoadArticlesAsync fallisce dopo un timeout di 10 secondi. Le prime due esecuzioni della pagina saranno estremamente lente, e visualizzeranno il messaggio "Articoli non disponibili".
Successivamente, il messaggio non cambierà - abbiamo creato il servizio in modo che fallisca sempre! - ma il caricamento della pagina sarà infinitamente più veloce. Se la pagina in questione fosse una dashboard, che invoca diversi servizi prima di mostrare un report all'utente, possiamo perfettamente intuire quanto questo possa rappresentare un beneficio per la user experience.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Implementare l'infinite scroll con QuickGrid in Blazor Server
Referenziare un @layer più alto in CSS
Evitare (o ridurre) il repo-jacking sulle GitHub Actions
Eseguire query per recuperare il padre di un record che sfrutta il tipo HierarchyID in Entity Framework
Sfruttare al massimo i topic space di Event Grid MQTT
Generare HTML a runtime a partire da un componente Razor in ASP.NET Core
Introduzione alle Container Queries
Paginare i risultati con QuickGrid in Blazor
Configurare il nome della run di un workflow di GitHub in base al contesto di esecuzione
Modificare i metadati nell'head dell'HTML di una Blazor Web App
Definire stili a livello di libreria in Angular
Configurare lo startup di applicazioni server e client con .NET Aspire