Implementare il pattern Circuit Breaker in ASP.NET Core MVC

di Marco De Sanctis, in ASP.NET Core,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi