Resiliência em aplicações .Net com Polly
Tags: dotnet, csharp, polly, dotnetcore
Published: 2021-04-12 15:41:52

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](https://github.com/marcosbelorio/polly-example).

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

```csharp
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();
        }
    }
}
```

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

```csharp
namespace HttpClientPollyExample.Interfaces
{
    public interface IHttpBinService
    {
        Task Get(int code);
    }
}
```

```csharp
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);
        }
    }
}
```

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.

```csharp
dotnet add package Polly
dotnet add package Polly.Extensions.Http
dotnet add package Microsoft.Extensions.Http.Polly
```

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.

```csharp
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));
}
```

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().

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8otv5hl4smm24z3u96x.PNG)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3n8r10vs50joh5bx4j9q.PNG)

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.