Blindando Aplicações no .NET 10: Um Guia Completo sobre Polly e Resiliência

Published: (February 9, 2026 at 09:29 AM EST)
6 min read
Source: Dev.to

Source: Dev.to – Blindando aplicações no .NET 10: um guia completo sobre Polly e resiliência

Resiliência à Prova de Falhas no .NET 10 com Polly

No desenvolvimento de software moderno — especialmente em arquiteturas de microsserviços e nuvem — a estabilidade não é definida pela ausência de falhas, mas pela capacidade do sistema de resistir e se recuperar delas.

Com a chegada do .NET 10 (e a evolução desde o .NET 8), a forma de implementar resiliência mudou drasticamente. As antigas Policies síncronas e pesadas deram lugar aos Resilience Pipelines: leves, performáticos e nativos para código assíncrono.

Este artigo explora como utilizar o poder do Polly em conjunto com o pacote Microsoft.Extensions.Http.Resilience para criar aplicações robustas.

1. O Novo Paradigma: Resilience Pipelines

Diferente das versões anteriores do Polly (v7 e inferiores), a nova arquitetura (v8+) foca em:

  • Zero‑Allocation (ou quase) – redução drástica no consumo de memória.
  • Pipeline Pattern – estratégias são encadeadas de forma lógica e fluente.
  • Integração Nativa – telemetria, logging e injeção de dependência já “saem da caixa”.

Instalação

Para projetos .NET modernos, instale os seguintes pacotes via NuGet:

dotnet add package Polly.Core
dotnet add package Microsoft.Extensions.Http.Resilience

Nota: o pacote Microsoft.Extensions.Http.Resilience é a recomendação oficial da Microsoft para aplicações web e APIs.

2. Estratégias Fundamentais de Resiliência

A seguir estão as principais estratégias de resiliência usando a sintaxe moderna do ResiliencePipelineBuilder.


A. Retry (Tentativa) com Jitter

O retry simples pode gerar o efeito thundering herd.
A solução padrão no Polly moderno é o back‑off exponencial com jitter.

using Polly;
using Polly.Retry;

ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        // Trata exceções de rede e códigos 5xx
        ShouldHandle = new PredicateBuilder().Handle(),
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(2),
        BackoffType = DelayBackoffType.Exponential, // Jitter incluído automaticamente
        OnRetry = static args =>
        {
            Console.WriteLine($"Tentativa {args.AttemptNumber} falhou. Retentando...");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

B. Circuit Breaker (Disjuntor)

Protege o serviço de destino interrompendo chamadas quando a taxa de falhas ultrapassa um limite.

var circuitOptions = new CircuitBreakerStrategyOptions
{
    FailureRatio      = 0.5,                                   // 50 % das requisições falham
    SamplingDuration  = TimeSpan.FromSeconds(30),               // nos últimos 30 s
    MinimumThroughput = 7,                                     // com no mínimo 7 requisições
    BreakDuration     = TimeSpan.FromSeconds(60)               // abre o circuito por 1 min
};

var pipeline = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(circuitOptions)
    .Build();

C. Fallback (Plano de Contingência)

Quando todas as tentativas falham (retry esgotado ou circuito aberto), o fallback define uma resposta segura – por exemplo, um valor padrão ou um dado em cache.

var pipeline = new ResiliencePipelineBuilder()
    .AddFallback(new FallbackStrategyOptions
    {
        FallbackAction = _ => Outcome.FromResult(
            new UserDto { Name = "Usuário Padrão" }),

        OnFallback = static _ =>
        {
            Console.WriteLine("Fallback acionado! Retornando dados provisórios.");
            return ValueTask.CompletedTask;
        }
    })
    .Build();

D. Hedging (Especulação)

Ideal para cenários de baixa latência. O hedging dispara chamadas paralelas (ou com pequeno atraso); a primeira que concluir com sucesso é usada, e as demais são canceladas.

var pipeline = new ResiliencePipelineBuilder()
    .AddHedging(new HedgingStrategyOptions
    {
        MaxHedgedAttempts = 2,
        Delay = TimeSpan.FromMilliseconds(500)   // dispara a 2ª tentativa se a 1ª demorar > 500 ms
    })
    .Build();

3. A Implementação “Gold Standard” no .NET 10

Em aplicações reais (.NET Core API, Worker Services, Blazor) você raramente constrói pipelines manualmente. Em vez disso, utiliza a Injeção de Dependência para decorar o HttpClient.

A Microsoft introduziu o AddStandardResilienceHandler, que aplica uma combinação pré‑configurada e otimizada de:

  • Rate Limiter
  • Total Timeout
  • Retry
  • Circuit Breaker
  • Attempt Timeout

Configuração no Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

// 1️⃣ Registro do HttpClient nomeado
builder.Services.AddHttpClient("CatalogoApi", client =>
{
    client.BaseAddress = new Uri("https://api.catalogo.com");
    client.Timeout = TimeSpan.FromSeconds(30); // Timeout do socket
})
// 2️⃣ Adicionando a Resiliência Padrão
.AddStandardResilienceHandler(options =>
{
    // Podemos customizar os padrões se necessário
    options.Retry.MaxRetryAttempts = 5;
    options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(15);
    options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
});

var app = builder.Build();

Com essa configuração, todas as chamadas feitas através do HttpClient nomeado “CatalogoApi” herdarão as políticas de resiliência definidas, garantindo que sua aplicação continue operando mesmo diante de falhas intermitentes ou degradações de serviço.

Consumindo o Serviço Resiliente

Sua classe de serviço não precisa saber que o Polly existe – a resiliência é transparente.

public class CatalogoService
{
    private readonly HttpClient _client;
    private readonly ILogger<CatalogoService> _logger;

    public CatalogoService(IHttpClientFactory factory, ILogger<CatalogoService> logger)
    {
        _client = factory.CreateClient("CatalogoApi");
        _logger = logger;
    }

    public async Task<Produto?> GetProdutoAsync(int id, CancellationToken ct)
    {
        // Se a rede falhar, o Polly intercepta, tenta de novo,
        // ou abre o circuito automaticamente aqui.
        try
        {
            return await _client.GetFromJsonAsync<Produto>($"/produtos/{id}", ct);
        }
        catch (Exception ex)
        {
            // Logar apenas se todas as estratégias de resiliência falharem
            _logger.LogError(ex, "Não foi possível obter o produto após múltiplas tentativas.");
            throw;
        }
    }
}

4. Cenários Avançados: Pipeline Registry

Às vezes, você precisa reutilizar a mesma estratégia de resiliência em vários lugares que não são necessariamente chamadas HTTP (ex.: acesso a arquivos, conexões Redis). Para isso, usamos o ResiliencePipelineProvider.

Registro

builder.Services.AddResiliencePipeline("meu-pipeline-banco", builder =>
{
    builder
        .AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
        .AddTimeout(TimeSpan.FromSeconds(5));
});

Uso injetado

public class BancoService(ResiliencePipelineProvider pipelineProvider)
{
    public async Task SalvarDadosAsync(string dados, CancellationToken ct)
    {
        // Recupera o pipeline pelo nome
        var pipeline = pipelineProvider.GetPipeline("meu-pipeline-banco");

        // Executa qualquer código arbitrário com resiliência
        await pipeline.ExecuteAsync(
            async token => await _database.ExecuteCommandAsync(dados, token),
            ct);
    }
}

5. Checklist de Melhores Práticas

  • Idempotência é chave
    Nunca use retry em métodos que não sejam idempotentes (ex.: POST para criar pedido) a menos que sua API suporte verificação de duplicidade. Se a primeira requisição criou o pedido mas a resposta falhou na rede, o retry pode gerar um pedido duplicado.

  • Timeouts em camadas

    Tipo de timeoutDescrição
    Attempt TimeoutTempo máximo de cada tentativa individual (curto).
    Total TimeoutTempo máximo para todo o processo (mais longo).
    Socket TimeoutConfiguração do HttpClient (tempo de espera no socket).
  • Circuit Breaker distribuído
    Lembre‑se de que o circuit breaker é local (mantido na memória da instância). Se você tem 10 réplicas da sua API no Kubernetes, cada uma terá seu próprio estado de circuito.

  • Não trate tudo
    Não use retry para erros de negócio (400 Bad Request, 403 Forbidden). A resiliência deve ser aplicada apenas a falhas transitórias, como:

    • 503 Service Unavailable
    • 408 Request Timeout
    • Falhas de rede (timeout, conexão perdida, etc.)

Conclusão

No .NET 10, a biblioteca Polly deixou de ser apenas um utilitário de terceiros para se tornar parte integrante da stack de rede da Microsoft. Ao utilizar Microsoft.Extensions.Http.Resilience e os novos Pipelines, você garante que sua aplicação seja capaz de suportar as instabilidades inerentes à nuvem com performance máxima e código limpo.

0 views
Back to Blog

Related posts

Read more »

Why Monoliths Make Sense for MVPs

Insight: Monolith vs Microservices for MVPs In the world of tech, especially for MVPs, choosing between a monolithic architecture and microservices can be a di...

Stop Overengineering in 2025

Why Your “Professional” Architecture is Killing Your Startup The Professionalism Paradox Most developers don’t fail because they lack technical skill; they fai...