Polly: Resiliência de aplicações com controle fino de falhas
Transformando falhas esperadas em comportamentos previsíveis e controlados dentro da aplicação
Em sistemas distribuídos mais modernos, as falhas não são meras exceções, são parte do fluxo normal. Chamadas HTTP instáveis, possuem timeouts, throttling, indisponibilidade momentânea de serviços externos e até falhas transitórias de banco de dados, esses são alguns dos cenários mais comuns. Nesse contexto, implementar resiliência deixa de ser um diferencial e passa a ser um requisito básico para se ter uma aplicação estável.
É exatamente aqui que entra o Polly.
O que é Polly?
Polly é uma biblioteca .NET focada em resiliência e tratamento de falhas transitórias, baseada no conceito de policies. Essas policies encapsulam diferentes tipos de estratégias como:
- Retry
- Circuit Breaker
- Timeout
- Bulkhead Isolation
- Fallback
A principal ideia é separar a lógica de negócio da lógica de resiliência, promovendo sempre um código mais limpo, testável e reutilizável.
Conceito central: Policies
Como falamos o coração do Polly são as suas policies. Elas funcionam como wrappers ao redor de uma operação:
var policy = Policy
.Handle<HttpRequestException>()
.RetryAsync(3);
await policy.ExecuteAsync(async () =>
{
await httpClient.GetAsync("https://api.externa.com");
});Aqui você define o que vai ser tratado (Handle) e como você vai reagir (RetryAsync).
O que é necessário para implementar?
1. Instalação
Via NuGet:
dotnet add package PollyPara integração com HttpClient:
dotnet add package Microsoft.Extensions.Http.Polly2. Integração com HttpClientFactory (recomendado)
No ASP.NET Core:
builder.Services.AddHttpClient("external-api")
.AddPolicyHandler(GetRetryPolicy());static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(3, retry =>
TimeSpan.FromSeconds(Math.Pow(2, retry)));
}Isso permite aplicar resiliência de forma centralizada em uma unica parte do codigo facilitando a manutenção e evolução.
Onde Polly é melhor utilizado?
1. Integrações externas (principal uso)
- APIs REST
- Serviços SOAP
- Gateways de pagamento
- APIs de terceiros
2. Banco de dados (falhas transitórias)
- Conexões instáveis
- Deadlocks
- Timeouts
3. Mensageria
- Kafka, RabbitMQ
- Falhas temporárias no broker
4. Sistemas distribuídos
- Microservices
- Arquiteturas orientadas a eventos
Comparação com bibliotecas e abordagens similares
Embora o Polly seja praticamente o padrão de mercado no ecossistema .NET, existem outras abordagens, tanto nativas quanto externas, que resolvem partes do mesmo problema.
A diferença principal está no nível de controle, composição e maturidade das estratégias de resiliência.
Polly vs Resilience nativa do .NET (Microsoft.Extensions.Resilience)
Com a evolução do .NET (principalmente .NET 8+), a Microsoft introduziu pipelines de resiliência nativos via:
Microsoft.Extensions.ResilienceSystem.Threading.RateLimiting
Exemplo simplificado:
builder.Services.AddResiliencePipeline("pipeline", builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2)
});
});Principais diferenças
Critério Polly Resilience nativa(.NET)
Maturidade Alta (anos de uso em produção) Recente
Flexibilidade Muito alta Alta, mas mais opinativa
Composição Livre (Policy.Wrap) Pipeline estruturado
Integração Excelente com HttpClient Nativa no ecossistema
Curva de aprendizado Média Baixa
Observabilidade Manual Melhor integradaAnálise técnica
- Polly oferece controle total, ideal para cenários complexos
- A abordagem nativa é mais opinionated, favorecendo padronização
- Internamente, a própria Microsoft vem utilizando conceitos inspirados no Polly
Em projetos novos, especialmente com .NET moderno, é comum ver:
- Uso direto da pipeline nativa
- Ou Polly integrado por baixo
Principais estratégias de resiliência
Retry
Reexecuta a operação em caso de falha.
Policy
.Handle<Exception>()
.RetryAsync(3);Problema que resolve:
- Falhas transitórias
Risco:
- Pode sobrecarregar o serviço se mal configurado
Wait and Retry(Backoff)
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));Benefício:
- Evita flood de requisições
- Dá tempo para o sistema se recuperar
Circuit Breaker
Policy
.Handle<Exception>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));Timeout
Policy.TimeoutAsync(2);Evita:
- Threads presas indefinidamente
Fallback
Policy<string>
.Handle<Exception>()
.FallbackAsync("valor padrão");Uso:
- Retornar dados default
- Evitar falha total
Bulkhead Isolation
Policy.BulkheadAsync(10, int.MaxValue);Objetivo:
- Limitar concorrência
- Evitar colapso do sistema
Benefícios do Polly
1. Separação clara de responsabilidades
Resiliência não polui sua regra de negócio.
2. Reutilização
As policies podem ser reutilizadas em vários pontos diferentes da aplicação.
3. Composição de estratégias
Você pode combinar múltiplas policies diferentes de acordo com a necessidade:
var policyWrap = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);4. Controle fino
Você decide:
- Quais exceções você quer tratar
- Quais respostas considera de fato uma falha
- Quantas tentativas serão feitas
- Tempo entre cada uma das tentativas
5. Integração nativa com .NET
Funciona muito bem em conjunto com outras bibliotecas como:
- HttpClientFactory
- Dependency Injection
- Minimal APIs
- Background Services
Comparação com abordagens similares
1. Retry manual (try/catch + loop)
for (int i = 0; i < 3; i++)
{
try
{
return await Call();
}
catch { }
}Problemas:
- Código duplicado
- Difícil manutenção
- Sem backoff estruturado
- Sem composição (circuit breaker, etc.)
Polly resolve tudo isso de forma declarativa.
2. Middleware customizado
Você pode criar middlewares para retry, mas:
- Não resolve granularidade por chamada
- Não cobre cenários como circuit breaker facilmente
- Mais complexo de manter
3. Bibliotecas nativas (ex: SqlClient resiliency)
Algumas libs já têm seu proprio mecanismo de retry embutido, mas:
- São muito específicas
- Pouco ou nada configuráveis
- Não compõem estratégias tornando-as flexíveis
Polly é genérico e extensível.
Pontos de atenção (muito importantes)
1. Retry em operações não idempotentes
Nunca use retry em operações como:
- POST de pagamento
- Criação de pedidos sem controle
Isso pode gerar duplicidade nos registros.
2. Backoff mal configurado
Retry agressivo pode:
- Derrubar as APIs externas
- Aumentar latência global da aplicação
3. Falta de logging
É uma boa prática sempre logar os retries:
onRetry: (result, time, retryCount, context) =>
{
_logger.LogWarning(
"Retry #{retryCount} após {time}s",
retryCount, time.TotalSeconds);
}4. Circuit breaker mal configurado
- Muito sensível → abre toda hora
- Muito tolerante → se torna inútil
5. Timeout + Retry
Combinar mal as policies pode gerar efeito cascata:
- Timeout curto + muitos retries = explosão de chamadas
Exemplo prático com Minimal API
Cenário
Uma API que consome um serviço externo de cotações.
Configuração
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("quotes-api", client =>
{
client.BaseAddress = new Uri("https://api.externa.com");
})
.AddPolicyHandler(GetPolicy());
var app = builder.Build();Policy composta
static IAsyncPolicy<HttpResponseMessage> GetPolicy()
{
var retry = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => (int)r.StatusCode >= 500)
.WaitAndRetryAsync(
3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(outcome, timespan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount}");
});
var circuitBreaker = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
return Policy.WrapAsync(retry, circuitBreaker);
}Endpoint Minimal API
app.MapGet("/quotes", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("quotes-api");
var response = await client.GetAsync("/quotes");
if (!response.IsSuccessStatusCode)
return Results.Problem("Erro ao buscar cotações");
var content = await response.Content.ReadAsStringAsync();
return Results.Ok(content);
});Evolução desse cenário
Você pode evoluir facilmente para:
- Adicionar fallback (cache local)
- Inserir métricas (Prometheus, Application Insights)
- Externalizar configuração (appsettings)
- Criar policies por contexto (ex: API crítica vs não crítica)
Quando NÃO usar Polly?
Apesar de poderoso, polly não é bala de prata.
Evite usar quando:
- A operação precisa falhar imediatamente (ex: autenticação)
- O custo da repetição é alto
- A operação não é segura para retry
Conclusão
Polly resolve um problema estrutural em aplicações modernas: a inevitabilidade de falhas. Ele permite tratar essas falhas de forma declarativa, reutilizável e altamente configurável, sem poluir a lógica de negócio.
Em ambientes distribuídos, especialmente quando falamos de microservices e integrações externas, o uso de Polly deixa de ser algo opcional e passa a ser parte essencial da montagem da sua arquitetura.
Se bem configurado, ele aumenta significativamente:
- Resiliência
- Estabilidade
- Observabilidade
- Controle operacional
Se mal configurado, pode amplificar problemas.
A diferença está na forma como você define suas policies.
Código-fonte do projeto
Para facilitar o entendimento, disponibilizei o projeto completo com o exemplo de Polly, já organizado com separação de responsabilidades e boas práticas:
👉 Baixar o projeto no GitHub neste link:
Quer ver mais conteúdos como este?
Se você trabalha com .NET, C# e backend, este blog seguirá trazendo conteúdos práticos e objetivos sobre o ecossistema Microsoft, com foco em código, boas práticas e decisões técnicas aplicadas ao mundo real.
Acompanhe para continuar aprofundando seu domínio sobre a plataforma .NET.