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
Migrare una service connection a workload identity federation in Azure DevOps
Evitare il flickering dei componenti nel prerender di Blazor 8
Supportare il sorting di dati tabellari in Blazor con QuickGrid
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API
Esportare ed analizzare le issue di GitHub con la CLI e GraphQL
Gestire i dati con Azure Cosmos DB Data Explorer
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
I più letti di oggi
- Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
- Creare una libreria CSS universale: Cards
- Eseguire script pre e post esecuzione di un workflow di GitHub