Polly: Resiliência de aplicações com controle fino de falhas

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 Polly

Para integração com HttpClient:

dotnet add package Microsoft.Extensions.Http.Polly

2. 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.Resilience
  • System.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                          integrada

Aná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.

Confira mais:

Fique por dentro das novidades

Assine nossa newsletter e receba as últimas atualizações e artigos diretamente em seu email.

Assinar gratuitamente