Chamar APIs externas é parte do dia a dia de qualquer aplicação moderna. O problema é que a rede falha — e quando falha, seu sistema precisa saber reagir. Combinar Refit (cliente HTTP tipado) com Polly (políticas de resiliência) é uma das formas mais elegantes de resolver isso em .NET.

O que é Refit?

Refit transforma interfaces C# em clientes HTTP. Em vez de montar HttpRequestMessage na mão, você define um contrato:

public interface IGitHubApi
{
    [Get("/users/{user}")]
    Task<GitHubUser> GetUser(string user);

    [Get("/users/{user}/repos")]
    Task<List<GitHubRepo>> GetRepos(string user);
}

O Refit gera a implementação automaticamente. Zero boilerplate.

O que é Polly?

Polly é uma biblioteca de resiliência que permite definir políticas para lidar com falhas:

  • Retry — tentar novamente após falha
  • Circuit Breaker — parar de chamar um serviço que está fora do ar
  • Timeout — cancelar chamadas lentas
  • Fallback — retornar valor padrão quando tudo falha
  • Bulkhead — limitar chamadas simultâneas

Configurando Refit + Polly com DI

1. Instale os pacotes

dotnet add package Refit.HttpClientFactory
dotnet add package Microsoft.Extensions.Http.Polly

2. Defina a interface do cliente

public interface IPaymentGateway
{
    [Post("/charges")]
    Task<ChargeResponse> CreateCharge([Body] ChargeRequest request);

    [Get("/charges/{id}")]
    Task<ChargeResponse> GetCharge(string id);

    [Post("/refunds")]
    Task<RefundResponse> Refund([Body] RefundRequest request);
}

3. Registre no Program.cs com políticas Polly

builder.Services
    .AddRefitClient<IPaymentGateway>()
    .ConfigureHttpClient(c =>
    {
        c.BaseAddress = new Uri("https://api.gateway.com/v1");
        c.DefaultRequestHeaders.Add("Authorization", "Bearer ...");
    })
    .AddTransientHttpErrorPolicy(p =>
        p.WaitAndRetryAsync(3, attempt =>
            TimeSpan.FromSeconds(Math.Pow(2, attempt))))
    .AddTransientHttpErrorPolicy(p =>
        p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Isso configura:

  • Retry com backoff exponencial: 2s, 4s, 8s entre tentativas
  • Circuit breaker: após 5 falhas consecutivas, para de chamar por 30 segundos

4. Use via injeção de dependência

public class PaymentService
{
    private readonly IPaymentGateway _gateway;

    public PaymentService(IPaymentGateway gateway)
    {
        _gateway = gateway;
    }

    public async Task<ChargeResponse> CobrarCliente(ChargeRequest request)
    {
        // Refit faz a chamada HTTP
        // Polly cuida do retry e circuit breaker automaticamente
        return await _gateway.CreateCharge(request);
    }
}

Nenhuma linha de código de resiliência no service. Tudo está configurado na camada de infraestrutura.

Políticas avançadas

Timeout por chamada

.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(10))

Fallback com valor padrão

var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .OrResult(r => !r.IsSuccessStatusCode)
    .FallbackAsync(new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent("{\"status\":\"unavailable\"}")
    });

Combinando múltiplas políticas (Policy Wrap)

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)));

var circuitBreaker = Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(15);

// Ordem importa: timeout → retry → circuit breaker
var wrapped = Policy.WrapAsync(timeout, retryPolicy, circuitBreaker);

builder.Services
    .AddRefitClient<IPaymentGateway>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.gateway.com/v1"))
    .AddPolicyHandler(wrapped);

Logging das políticas

Polly permite hooks em cada evento. Isso é essencial para observabilidade:

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(
        3,
        attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
        onRetry: (exception, delay, attempt, context) =>
        {
            Log.Warning(
                "Tentativa {Attempt} falhou. Retentando em {Delay}s. Erro: {Error}",
                attempt, delay.TotalSeconds, exception.Message);
        });

Quando usar cada política

CenárioPolítica
API instável com falhas intermitentesRetry com backoff
Serviço externo completamente foraCircuit Breaker
API lenta que trava chamadasTimeout
Funcionalidade não-críticaFallback
Proteção contra sobrecargaBulkhead
Produção realCombinação de todas (Policy Wrap)

Erros comuns

1. Retry infinito sem circuit breaker Se a API está fora do ar, retry sozinho vai apenas aumentar a latência e consumir recursos. Sempre combine retry com circuit breaker.

2. Não usar backoff exponencial Retry fixo (ex: a cada 1 segundo) pode sobrecarregar a API que está se recuperando. Use backoff exponencial com jitter:

// Com jitter para evitar thundering herd
var jitter = new Random();
p.WaitAndRetryAsync(3, attempt =>
    TimeSpan.FromSeconds(Math.Pow(2, attempt))
    + TimeSpan.FromMilliseconds(jitter.Next(0, 1000)));

3. Esquecer de tratar o Polly no pipeline de testes Em testes de integração, desabilite as políticas ou use timeouts curtos para não deixar os testes lentos.

Conclusão

Refit elimina o boilerplate de clientes HTTP. Polly adiciona resiliência sem poluir sua lógica de negócio. Juntos, formam um padrão robusto para qualquer aplicação .NET que consome APIs externas.

Na DevPlus, usamos essa combinação em todos os projetos que integram com gateways de pagamento, ERPs e APIs de terceiros. O resultado: menos downtime, melhor observabilidade e código limpo.

Está integrando com APIs externas e precisa de resiliência? Fale com a DevPlus — podemos ajudar.