DEV Community

Marcos Belorio
Marcos Belorio

Posted on

Resiliência em aplicações .Net com Polly

Neste artigo vou abordar o uso da lib Polly para criarmos políticas de retry e circuit breaker em requisições http dentro de nossa aplicação.

Problema a ser solucionado

Frequentemente precisamos lidar com requisições http dentro de nossas aplicações para consumir dados fornecidos por aplicações externas, as famosas requisições para apis, nesse contexto a lib Polly ajuda a criar comportamentos através da criação de políticas que tornam a nossa aplicação mais resiliente a possíveis falhas dessas requisições.

Política de retry

Essa política serve para lidarmos principalmente com indisponibilidades momentâneas, vamos imaginar que realizamos uma requisição a uma api e por algum motivo ela está sendo muito requisitada e está apresentando lentidões para responder, nesse cenário estamos propícios a talvez receber um erro 408 de timeout da api, mas esse cenário pode ser momentâneo, talvez tentando mais uma vez ou depois de algum tempo, pode ser que a api esteja respondendo normalmente. Nesse cenário a política de retry se encaixa muito bem, podemos definir uma quantidade de tentativas em um espaço de tempo para que seja possível obter um retorno de sucesso da requisição.

Política de circuit breaker

Você já deve ter utilizado algum aplicativo que estava apresentando lentidões para carregar os dados e você efetuou o comando de atualizar os dados (geralmente é o movimento de arrastar para baixo) e o aplicativo respondeu instantaneamente que não foi possível atualizar. Provavelmente o aplicativo estava com a política de circuit breaker ativada. Essa política auxilia em evitar tentativas desnecessárias, para que não seja sobrecarregado ainda mais uma aplicação já sobrecarregada. O desenvolvedor define uma quantidade limite de erros em sequência para ativar o circuit breaker, uma vez ativado, ele fica com o circuito aberto e não realiza novas requisições até que o circuito seja fechado novamente, esse tempo em que o circuito fica aberto é também configurado na política.

Demonstração

O exemplo que eu irei mostrar a seguir também está publicado no GitHub.

Vamos criar um novo projeto web api e criar uma controller com um endpoint para podermos realizar os nossos testes.

namespace HttpClientPollyExample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class HttpBinController : ControllerBase
    {
        private readonly IHttpBinService _service;

        public HttpBinController(IHttpBinService service)
        {
            _service = service;
        }

        [HttpGet("{code:int}")]
        public async Task<IActionResult> Get([FromRoute] int code)
        {
            await _service.Get(code);
            return Ok();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Vamos agora criar a classe service e sua interface que está sendo passada via injeção de dependência no construtor da controller.

namespace HttpClientPollyExample.Interfaces
{
    public interface IHttpBinService
    {
        Task Get(int code);
    }
}
Enter fullscreen mode Exit fullscreen mode
namespace HttpClientPollyExample.Services
{    
    public class HttpBinService : IHttpBinService
    {
        private readonly HttpClient _httpClient;
        public HttpBinService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task Get(int code)
        {
            var response = await _httpClient.GetAsync($"http://httpbin.org/status/{code}");
            Console.WriteLine(response.IsSuccessStatusCode);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Como podemos ver no código da classe service, estamos fazendo uma requisição http para a api httpbin (uma ótima api disponível para realizar testes) e passando o parâmetro code na rota, esse endpoint irá forçar o status code do retorno conforme o code que passarmos na rota, exemplo, se passarmos o valor 200 o endpoint irá retornar status code 200, se passarmos 500, irá retornar status code 500. Isso irá nos ajudar a simular os casos de uso das políticas do polly.

Agora vem a parte mais importante, iremos configurar o Polly em nossa aplicação. Primeiro vamos adicionar os pacotes necessários.

dotnet add package Polly
dotnet add package Polly.Extensions.Http
dotnet add package Microsoft.Extensions.Http.Polly
Enter fullscreen mode Exit fullscreen mode

Vamos agora criar as duas configurações das políticas de retry e circuit breaker. Por ser apenas uma demo eu adicionei na Startup.cs pois será lá que iremos utilizá-las.

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int retryCount)
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, 
    int durationOfBreakInSeconds)
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(exceptionsAllowedBeforeBreaking, TimeSpan.FromSeconds(durationOfBreakInSeconds));
}
Enter fullscreen mode Exit fullscreen mode

Vamos analisar as configurações, as duas políticas foram setadas com o handler HandleTransientHttpError(), se lermos a descrição dele vamos ver que a configuração irá valer para os seguintes erros:

  • Falhas de rede (System.Net.Http.HttpRequestException)
  • HTTP 5XX status codes (server errors)
  • HTTP 408 status code (request timeout)

Na política de retry passamos a quantidade de tentativas que serão realizadas e o intervalo de tempo entre elas, para isso setamos o valor TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) que irá dobrar o tempo entre cada tentativa começando por 2 segundos.

Na política de circuit breaker passamos a quantidade de exceções em sequência que serão aceitas antes de ativar o circuit breaker e o tempo que o circuito ficará aberto caso ele tenha sido ativado.

Por último vamos aplicar as duas políticas a nossa service que criamos utilizando AddHttpClient() e a extensão AddPolicyHandler().

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddHttpClient<IHttpBinService, HttpBinService>()
        .AddPolicyHandler(GetRetryPolicy(retryCount: 3))
        .AddPolicyHandler(GetCircuitBreakerPolicy(exceptionsAllowedBeforeBreaking: 5, durationOfBreakInSeconds: 30));
}
Enter fullscreen mode Exit fullscreen mode

Setamos para a política de retry 3 tentativas e para a política de circuit breaker 5 erros em sequência para ativá-lo e 30 segundos para o circuito fechar após aberto, vamos testar agora.

Execute a aplicação e chame o endpoint passando o valor 500 para o parâmetro code [GET]http://localhost:5000/httpbin/500 e veja o resultado no console.
Alt Text

O endpoint da api httpbin está retornando erro status code 500 e por isso foi tentado mais três vezes, conforme destacado na print, com isso conseguimos testar nossa política de retry.

Agora execute mais uma vez o endpoint passando o code 500 e veja o resultado novamente no console.
Alt Text

Como já tínhamos recebidos 4 erros em sequência no teste anterior, executamos a quinta requisição com erro e o circuit breaker foi ativado retornando uma exceção para a sexta chamada. Você pode tratar a exceção Polly.CircuitBreaker.BrokenCircuitException para ter um comportamento mais amigável na aplicação.

Latest comments (0)