Padrão Retry com Polly e ASP.NET Core
Fala dev .NET, tudo bem? No artigo de hoje vou explorar o conceito de resiliência e como o Padrão Retry e a biblioteca Polly podem ajudar nisso. Também vou relacionar esse tema com arquitetura de microsserviços, onde sua importância aumenta ainda mais.
Introdução a Resiliência
É importante deixar claro que falhas podem ocorrer independente da arquitetura utilizada, já que de alguma forma sistemas na maioria das vezes dependem de componentes externos, como bancos de dados ou APIs terceiras.
Porém, quando se trabalha com arquiteturas baseadas em microserviços, é fundamental entender que existem maior espaço para falhas acontecerem. Seja um serviço temporariamente indisponível ou uma falha na rede, esses são problemas comuns que precisamos aprender a lidar. É aí que entra o conceito de resiliência.
A resiliência é a capacidade de um sistema se recuperar rapidamente de falhas e continuar fornecendo suas funcionalidades de maneira correta. Isso é especialmente crítico em microserviços, pois falhas em um único serviço podem se propagar e afetar toda a aplicação.
A ideia não é evitar falhas. Afinal, isso seria impossível! O objetivo é minimizar o impacto que essas falhas podem ter. Para isso, podemos utilizar técnicas e padrões específicos, como o Padrão Retry, que vou explorar agora.
Sobre o padrão Retry
O Padrão Retry é uma estratégia simples, mas poderosa, para aumentar a resiliência de um sistema. A ideia é tentar novamente uma operação que falhou, na esperança de que ela possa ter sucesso nas tentativas subsequentes.
Por exemplo, se um microserviço está temporariamente indisponível devido a uma sobrecarga ou outra razão, tentar a operação novamente após um curto período de tempo pode resolver o problema. No entanto, é importante ter cuidado para não sobrecarregar ainda mais o serviço com tentativas excessivas de retry.
Conhecendo o Polly
Com esses conceitos em mente, vamos falar sobre o Polly. Esta biblioteca .NET ajuda a implementar políticas de resiliência, como o padrão Retry, de uma maneira fácil e descomplicada.
No Polly, você pode configurar uma política de retry para especificar o número de tentativas e o tempo entre elas. Além do padrão Retry, o Polly suporta muitas outras políticas, como Circuit Breaker e Fallback, que permitem que você construa sistemas altamente resilientes.
Polly e ASP.NET Core
Agora, vamos ver como usar o Polly com o ASP.NET Core. A configuração é bem simples e pode ser feita diretamente no Program.cs do seu projeto. Além disso, Polly se integra perfeitamente com o HttpClientFactory do ASP.NET Core, permitindo que você aplique políticas de resiliência às suas chamadas HTTP.
O primeiro passo é instalar o pacote:
Microsoft.Extensions.Http.Polly
Com isso, vamos no Program.cs. Porém, antes de mostrar a configuração, vou apresentar brevemente nosso serviço de integração simples que criei. A API com a qual estou me comunicando é um projeto simples em ASP.NET Core, que só responde o código 204 (Sucesso, No Content).
Interface do serviço:
public interface IMyIntegrationService
{
Task<bool> Sync(CustomerIntegrationModel model);
}
Implementação do serviço:
public class MyIntegrationService : IMyIntegrationService
{
private readonly HttpClient _httpClient;
public MyIntegrationService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<bool> Sync(CustomerIntegrationModel model)
{
var response = await _httpClient.PostAsJsonAsync($"http://localhost:5105/api/fakes/", model);
return response.IsSuccessStatusCode;
}
}
Você pode estar se perguntando o que o HttpClient faz aí, mas não se preocupe. Na configuração que faremos a seguir o HttpClient vai estar disponível via injeção de dependência com as políticas de resiliência aplicadas.
O cenário é de integração com um serviço terceiro para sincronizar dados ao ter um novo cadastro de cliente. Com o serviço apresentado, vamos à configuração no Program.cs.
builder.Services.AddScoped<IMyIntegrationService, MyIntegrationService>();
builder.Services.AddHttpClient<IMyIntegrationService, MyIntegrationService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler(
HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(2, retryAttempts => TimeSpan.FromSeconds(Math.Pow(2, retryAttempts)))
);
A primeira linha se refere à injeção de dependência do serviço, logo não tem a ver diretamente com a resiliência utilizando Polly. Já da terceira em diante, configuramos o uso de HttpClient com políticas de re-tentativa para nosso serviço, definindo uma quantidade de tentativas igual a 2, com tempo aumentando de forma exponencial.
Com a nossa configuração feita, mostro o uso do serviço no Controller, fazendo um testes: um com o serviço com o qual vamos integrar em execução, e outro com ele desligado.
[ApiController]
[Route("api/customers")]
public class CustomersController : ControllerBase
{
private readonly IMyIntegrationService _service;
public CustomersController(IMyIntegrationService service)
{
_service = service;
}
[HttpPost]
public async Task<IActionResult> Post(CustomerIntegrationModel customer)
{
await _service.Sync(customer);
return Ok();
}
}
E logo abaixo o resultado de nossos testes.
Note que a requisição foi bem sucedida nesse caso, já que o serviço está em execução.
Agora vou parar a execução do serviço e vamos ver o resultado.
Notamos agora que houveram duas tentativas adicionais, com a exceção sendo lançada em seguida.
Com isso, demonstramos o uso da biblioteca Polly para a implementação do padrão Retry, que resulta em maior resiliência em nosso serviços.
Conclusão
E é isso aí, dev .NET! Resiliência e padrão Retry são conceitos-chave para construir microserviços robustos e confiáveis. E com o Polly e o ASP.NET Core, você tem as ferramentas certas para colocar tudo isso em prática.
Um padrão muito comum de ser utilizado junto ao Retry é o Circuit Breaker. Pode ser tema para um artigo futuro!
Espero que tenha curtido. Até a próxima!