[CS] CARLOS SEIDL EN
Artigos

API Gateway + BFF em .NET: Um Gateway, Vários Clientes

Como combinar API Gateway com YARP e BFF em .NET 10 para dar a cada cliente os dados certos, com autenticação e rate limiting centralizados.

Por 13 min de leitura
  • API Gateway
  • BFF
  • YARP
  • .NET 10
Capa do artigo: API Gateway + BFF em .NET: Um Gateway, Vários Clientes

Por que usar API Gateway e BFF em microsserviços

O problema e a solução: sem e com Gateway + BFF
O problema e a solução: sem e com Gateway + BFF

Quando o sistema deixa de ser um monólito, o front passa a conversar com vários serviços: usuário, produto, pedido, pagamento. Sem uma camada intermediária, cada cliente (web, mobile, tablet) precisa conhecer o endereço de cada serviço, autenticar em todos, juntar respostas e lidar com falhas parciais. O resultado é lógica de orquestração duplicada em cada app e serviços internos expostos na internet.

Neste artigo, continuação da série (no artigo sobre Monólito vs Microservices vimos por que dividir; aqui vemos como o cliente consome essa divisão), combinamos dois padrões: o API Gateway, que cuida do que é comum a todos, e o BFF (Backend for Frontend), que entrega a cada cliente exatamente os dados de que ele precisa.

API Gateway e BFF: conceitos e quem faz o quê

API Gateway: ponto único de entrada. Trata preocupações transversais, sem regra de negócio:

  • Autenticação e autorização
  • Rate limiting
  • Roteamento
  • Logging, métricas e tracing

BFF (Backend for Frontend): um backend por tipo de cliente (Web BFF, Mobile BFF, Tablet BFF). Ele:

  • Agrega chamadas a vários microsserviços em uma resposta
  • Formata o payload para a necessidade da tela
  • Escala de forma independente, conforme a demanda daquele cliente

Quem faz o quê

  • Autenticação e rate limit: Gateway
  • Roteamento por cliente: Gateway
  • Agregar dados de serviços: BFF
  • Adaptar o formato ao cliente: BFF
  • Regra de negócio: nenhum dos dois; fica nos microsserviços

Regra de bolso: se a lógica serve a todos os clientes, é do Gateway; se serve a um cliente específico, é do BFF; se é regra de negócio, é do microsserviço.

Em código, a diferença entre os dois papéis aparece logo:

C#
// Gateway: não conhece o domínio; autentica, limita e encaminha



app.UseAuthentication();



app.UseAuthorization();



app.UseRateLimiter();



app.MapReverseProxy();
C#
// BFF: conhece a tela; agrega e devolve uma resposta sob medida



app.MapGet("/order/{id}", async (



    string id,



    IOrderClient orders,



    ITrackingClient tracking,



    CancellationToken ct) =>



{



    var orderTask = orders.GetOrderAsync(id, ct);



    var trackingTask = tracking.GetTrackingAsync(id, ct);







    await Task.WhenAll(orderTask, trackingTask);







    return Results.Ok(new



    {



        Order = orderTask.Result,



        Tracking = trackingTask.Result



    });



});

O Gateway termina em MapReverseProxy(): nenhuma linha dele sabe o que é um pedido. O BFF é o contrário: só ele monta a resposta da tela.

Quem faz o quê: Gateway, BFF e microsserviço
Quem faz o quê: Gateway, BFF e microsserviço

YARP (Yet Another Reverse Proxy): biblioteca da Microsoft, construída sobre o ASP.NET Core, para criar reverse proxies e gateways em .NET. Em vez de um produto fechado, é um conjunto de blocos que você configura (ou estende em C#) dentro de uma aplicação web comum.

Um reverse proxy fica entre o cliente e os servidores internos: recebe a requisição, decide para onde ela vai e devolve a resposta. O cliente nunca conhece os endereços internos. Os conceitos do YARP são poucos:

  • Route: regra de entrada. Define o que casa (caminho, host, método, headers, query) e para qual cluster enviar. Também recebe políticas por rota: autorização, CORS, rate limiting, timeout.
  • Cluster: grupo lógico de destinations (instâncias) que atendem uma rota. É aqui que ficam load balancing, health checks e afinidade de sessão.
  • Destination: o endereço de uma instância (por exemplo, http://mobile-bff:8080/).
  • Transform: ajuste feito na requisição ou resposta proxiada (reescrever caminho, adicionar header, repassar IP original). A requisição original não é alterada.
  • Configuração dinâmica: a seção ReverseProxy pode ser recarregada quando o arquivo muda, sem reiniciar o gateway. Também é possível carregar de várias fontes (arquivo, memória, provider próprio).

Na prática, uma rota e um cluster mínimos são só isto:

JSON
"ReverseProxy": {



  "Routes": {



    "orders": {



      "ClusterId": "orders-cluster",



      "Match": { "Path": "/orders/{**catch-all}" }



    }



  },



  "Clusters": {



    "orders-cluster": {



      "Destinations": {



        "d1": { "Address": "http://orders:8080/" }



      }



    }



  }



}

Toda requisição que casar com /orders/... vai para o cluster orders-cluster, e o cluster decide qual destination atende.

Por que YARP e não outro proxy: roda no mesmo stack .NET do time, usa o pipeline e as políticas do ASP.NET Core (autenticação, rate limiter, CORS) e permite lógica customizada em C#, sem aprender outra linguagem de configuração.

Capacidades principais

  • Roteamento por caminho, host, método, header ou query
  • Load balancing entre destinations, com health checks
  • Transforms de URL e de headers
  • Integração com autenticação, autorização, CORS e rate limiting do ASP.NET Core
  • Hot reload da configuração de rotas, sem reiniciar
  • Extensível em C# (políticas de load balancing, transforms, providers de configuração)
  • Open source, mantido pela Microsoft

Quando o YARP é uma boa escolha

  • Gateway de entrada para microsserviços .NET
  • Ponto único de entrada com reescrita de rotas e URLs
  • Alternativa leve e integrada ao stack, sem operar um gateway à parte
  • Base de um BFF que precisa de proxy mais lógica própria

Atenção: o YARP encaminha e reescreve requisições; ele não agrega respostas de vários serviços. Agregar (uma tela, vários serviços) é papel do BFF, como no exemplo 5.2. YARP no Gateway e um BFF atrás dele é exatamente a combinação deste artigo.

Casos de uso do YARP

  1. API Gateway para microsserviços: ponto único de entrada que roteia cada requisição para o serviço certo (exemplo 5.1).
  2. Load balancing entre instâncias: distribui as requisições entre várias instâncias do mesmo serviço, por exemplo em round-robin (5.1.1).
  3. Autenticação e autorização centralizadas: o token é validado uma vez no Gateway e só requisições autenticadas chegam aos serviços (5.1).
  4. Roteamento e reescrita de caminho: transforma URLs, query strings e headers antes de encaminhar, como expor /api/v1/products e chamar /api/products internamente (5.1.2).
  5. Gateway por tipo de cliente (BFF): um ponto de entrada especializado por cliente. O proxy encaminha; a agregação é código do BFF (5.2).
  6. Traffic shaping e rate limiting: limita requisições por cliente e rejeita o excesso com 429 Too Many Requests (5.1).
  7. Observabilidade e logging centralizados: logs, traces e métricas coletados em um único ponto (seção 5).
  8. Migração e releases graduais: migração do monólito para microsserviços rota a rota, canary release, rollout gradual e teste A/B. O YARP não traz divisão percentual de tráfego pronta; isso exige roteamento por header/cookie ou uma política de load balancing própria (ILoadBalancingPolicy).
Casos de uso do YARP
Casos de uso do YARP

Arquitetura do fluxo de requisição com Gateway e BFF

Fluxo de uma requisição:

Texto
Cliente (Mobile/Web/Tablet)



        │  HTTPS



        ▼



   API Gateway  ── autentica, limita, roteia, registra logs



        │



        ├── /web/**    → Web BFF



        ├── /mobile/** → Mobile BFF



        └── /tablet/** → Tablet BFF



                              │



                              ├── Order Service



                              ├── Tracking Service



                              └── Notification Service
Fluxo da requisição do cliente ao microsserviço
Fluxo da requisição do cliente ao microsserviço

Pontos de projeto:

  • Rede interna: apenas o Gateway é público. BFFs e microsserviços ficam atrás dele.
  • Chamadas paralelas: o BFF dispara as chamadas aos serviços ao mesmo tempo, não em sequência.
  • Falha parcial: se o serviço de notificações cair, a tela de pedido ainda deve abrir.
  • Contrato por cliente: cada BFF evolui seu contrato sem afetar os outros clientes.

Em .NET, a escolha natural para o Gateway é o YARP (Yet Another Reverse Proxy), e para o BFF, uma Minimal API ou controllers com HttpClient tipado.

Exemplo prático de API Gateway com YARP e BFF em .NET

O código completo deste artigo está no GitHub: https://github.com/ceseidl/api-gateway-bff-dotnet

4.1. Gateway com YARP

C#
// Gateway/Program.cs



using System.Threading.RateLimiting;



using Microsoft.AspNetCore.Authentication.JwtBearer;



using Yarp.ReverseProxy.Transforms;







var builder = WebApplication.CreateBuilder(args);







builder.Services



    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)



    .AddJwtBearer();







builder.Services.AddAuthorization();







builder.Services.AddRateLimiter(options =>



{



    options.RejectionStatusCode =



        StatusCodes.Status429TooManyRequests;







    options.AddPolicy("per-user", httpContext =>



    {



        var key = httpContext.User.Identity?.Name



            ?? httpContext.Connection.RemoteIpAddress?.ToString()



            ?? "anon";







        return RateLimitPartition.GetFixedWindowLimiter(



            key,



            _ => new FixedWindowRateLimiterOptions



            {



                PermitLimit = 100,



                Window = TimeSpan.FromMinutes(1)



            });



    });



});







builder.Services



    .AddReverseProxy()



    .LoadFromConfig(



        builder.Configuration.GetSection("ReverseProxy"));







var app = builder.Build();







app.UseAuthentication();



app.UseAuthorization();



app.UseRateLimiter();







app.MapReverseProxy();







app.Run();
JSON
// Gateway/appsettings.json



{



  "ReverseProxy": {



    "Routes": {



      "mobile": {



        "ClusterId": "mobile-bff",



        "AuthorizationPolicy": "default",



        "RateLimiterPolicy": "per-user",



        "Match": { "Path": "/mobile/{**catch-all}" },



        "Transforms": [ { "PathRemovePrefix": "/mobile" } ]



      },



      "web": {



        "ClusterId": "web-bff",



        "AuthorizationPolicy": "default",



        "RateLimiterPolicy": "per-user",



        "Match": { "Path": "/web/{**catch-all}" },



        "Transforms": [ { "PathRemovePrefix": "/web" } ]



      }



    },



    "Clusters": {



      "mobile-bff": {



        "Destinations": {



          "d1": { "Address": "http://mobile-bff:8080/" }



        }



      },



      "web-bff": {



        "Destinations": {



          "d1": { "Address": "http://web-bff:8080/" }



        }



      }



    }



  }



}

O Gateway não conhece pedido, rastreio nem notificação. Ele só autentica, limita e encaminha.

4.1.1. Cluster com load balancing e health checks

Quando o BFF roda em mais de uma instância, o cluster passa a ter várias destinations e o YARP escolhe para qual enviar:

JSON
"Clusters": {



  "mobile-bff": {



    "LoadBalancingPolicy": "RoundRobin",



    "HealthCheck": {



      "Active": {



        "Enabled": true,



        "Interval": "00:00:10",



        "Timeout": "00:00:05",



        "Policy": "ConsecutiveFailures",



        "Path": "/health"



      },



      "Passive": {



        "Enabled": true,



        "Policy": "TransportFailureRate",



        "ReactivationPeriod": "00:00:30"



      }



    },



    "Destinations": {



      "d1": { "Address": "http://mobile-bff-1:8080/" },



      "d2": { "Address": "http://mobile-bff-2:8080/" }



    }



  }



}
  • Políticas de load balancing embutidas: PowerOfTwoChoices (padrão), RoundRobin, LeastRequests, Random e FirstAlphabetical (útil para failover entre dois destinos). Dá para criar a sua implementando ILoadBalancingPolicy.
  • Health check ativo: o YARP chama periodicamente o endpoint de saúde (/health) de cada destination e tira do rodízio as que falham.
  • Health check passivo: observa as respostas reais e desativa temporariamente a destination com falhas, reativando após o ReactivationPeriod.
  • Afinidade de sessão (SessionAffinity) mantém o mesmo cliente na mesma instância, quando o BFF guarda estado local. Prefira BFFs sem estado.
YARP: Route, Cluster e Destinations
YARP: Route, Cluster e Destinations

4.1.2. Transforms: reescrevendo a requisição

No exemplo anterior, PathRemovePrefix já tira o /mobile antes de chamar o BFF. Outros usos comuns:

JSON
"Transforms": [



  { "PathRemovePrefix": "/mobile" },



  { "RequestHeader": "X-Client", "Set": "mobile-app" },



  { "X-Forwarded": "Append" }



]

Por padrão, o YARP já envia X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host, para o BFF saber o IP e o host originais do cliente. Transforms de corpo não existem no YARP; se precisar, use um middleware. No repositório deste artigo, o appsettings.json do Gateway já reúne as rotas e o cluster das seções 4.1, 4.1.1 e 4.1.2.

4.1.3. Estendendo em C

Transforms também podem ser adicionados por código, por exemplo para propagar a identidade do usuário já validada no Gateway:

C#
builder.Services



    .AddReverseProxy()



    .LoadFromConfig(



        builder.Configuration.GetSection("ReverseProxy"))



    .AddTransforms(context =>



    {



        // Só para rotas que exigem autenticação



        var policy = context.Route.AuthorizationPolicy;



        if (string.IsNullOrEmpty(policy))



            return;







        context.AddRequestTransform(transform =>



        {



            var userId = transform.HttpContext.User



                .FindFirst("sub")?.Value;



            var headers = transform.ProxyRequest.Headers;







            if (userId is not null)



                headers.TryAddWithoutValidation(



                    "X-User-Id", userId);







            return ValueTask.CompletedTask;



        });



    });

Importante: o BFF só deve confiar em X-User-Id se estiver numa rede onde apenas o Gateway consegue chamá-lo. Caso contrário, valide o token também no BFF.

No repositório, este trecho já está integrado ao Program.cs do Gateway, encadeado no AddReverseProxy.

Mobile BFF agregando três serviços em paralelo
Mobile BFF agregando três serviços em paralelo

4.2. Mobile BFF: agregando serviços

C#
// MobileBff/Contracts/OrderDetailsResponse.cs



public sealed record OrderDetailsResponse(



    OrderDto Order,



    TrackingDto? Tracking,



    IReadOnlyList<NotificationDto> Notifications);
C#
// MobileBff/Endpoints/OrderEndpoints.cs



public static class OrderEndpoints



{



    public static IEndpointRouteBuilder MapOrderEndpoints(



        this IEndpointRouteBuilder app)



    {



        app.MapGet("/order/{orderId}", GetOrderDetails);



        return app;



    }







    private static async Task<IResult> GetOrderDetails(



        string orderId,



        IOrderClient orders,



        ITrackingClient tracking,



        INotificationClient notifications,



        ILogger<Program> logger,



        CancellationToken ct)



    {



        var orderTask = orders.GetOrderAsync(orderId, ct);



        var trackingTask = tracking.GetTrackingAsync(orderId, ct);



        var notificationsTask =



            notifications.GetNotificationsAsync(orderId, ct);







        var order = await orderTask;



        if (order is null)



            return Results.NotFound();







        // Dados secundários: falha parcial não derruba a tela



        var trackingResult = await SafeAsync(



            trackingTask, logger, "tracking");



        var notificationsResult = await SafeAsync(



            notificationsTask, logger, "notifications")



            ?? Array.Empty<NotificationDto>();







        return Results.Ok(new OrderDetailsResponse(



            order, trackingResult, notificationsResult));



    }







    private static async Task<T?> SafeAsync<T>(



        Task<T> task, ILogger logger, string source)



    {



        try { return await task; }



        catch (Exception ex) when (



            ex is HttpRequestException or TaskCanceledException)



        {



            logger.LogWarning(ex,



                "Falha parcial ao consultar {Source}", source);



            return default;



        }



    }



}

4.3. Registro dos clientes tipados

C#
// MobileBff/Program.cs



var builder = WebApplication.CreateBuilder(args);



var services = builder.Configuration.GetSection("Services");







builder.Services



    .AddHttpClient<IOrderClient, OrderClient>(c =>



        c.BaseAddress = new Uri(services["Order"]!))



    .AddStandardResilienceHandler();







builder.Services



    .AddHttpClient<ITrackingClient, TrackingClient>(c =>



        c.BaseAddress = new Uri(services["Tracking"]!))



    .AddStandardResilienceHandler();







builder.Services



    .AddHttpClient<INotificationClient, NotificationClient>(c =>



        c.BaseAddress = new Uri(services["Notification"]!))



    .AddStandardResilienceHandler();







var app = builder.Build();



app.MapOrderEndpoints();



app.Run();

Pontos importantes do código

  • As três chamadas começam juntas; o tempo total é o da mais lenta, não a soma.
  • Pedido é essencial (sem ele, 404). Rastreio e notificações são complementares e degradam com elegância.
  • CancellationToken propagado: se o app mobile desistir da requisição, as chamadas internas são canceladas.
  • AddStandardResilienceHandler (Microsoft.Extensions.Http.Resilience) adiciona retry, circuit breaker e timeout por cliente.

4.4. Contratos e clientes de apoio

Os exemplos anteriores usam os tipos abaixo. Eles completam o BFF para compilar e rodar.

C#
// MobileBff/Contracts/Dtos.cs



public sealed record OrderDto(



    string Id, string Status, decimal Total);







public sealed record TrackingDto(string Status, string Location);







public sealed record NotificationDto(string Text);
C#
// MobileBff/Clients/Clients.cs



using System.Net;







public interface IOrderClient



{



    Task<OrderDto?> GetOrderAsync(



        string orderId, CancellationToken ct);



}







public interface ITrackingClient



{



    Task<TrackingDto?> GetTrackingAsync(



        string orderId, CancellationToken ct);



}







public interface INotificationClient



{



    Task<IReadOnlyList<NotificationDto>> GetNotificationsAsync(



        string orderId, CancellationToken ct);



}







public sealed class OrderClient(HttpClient http)



    : IOrderClient



{



    public async Task<OrderDto?> GetOrderAsync(



        string orderId, CancellationToken ct)



    {



        using var response =



            await http.GetAsync($"orders/{orderId}", ct);







        if (response.StatusCode == HttpStatusCode.NotFound)



            return null;







        response.EnsureSuccessStatusCode();



        return await response.Content



            .ReadFromJsonAsync<OrderDto>(ct);



    }



}







public sealed class TrackingClient(HttpClient http)



    : ITrackingClient



{



    public Task<TrackingDto?> GetTrackingAsync(



        string orderId, CancellationToken ct) =>



        http.GetFromJsonAsync<TrackingDto>(



            $"tracking/{orderId}", ct);



}







public sealed class NotificationClient(HttpClient http)



    : INotificationClient



{



    public async Task<IReadOnlyList<NotificationDto>>



        GetNotificationsAsync(



            string orderId, CancellationToken ct) =>



        await http.GetFromJsonAsync<List<NotificationDto>>(



            $"notifications/{orderId}", ct) ?? [];



}
JSON
// MobileBff/appsettings.json



{



  "Services": {



    "Order": "http://order-service:8080/",



    "Tracking": "http://tracking-service:8080/",



    "Notification": "http://notification-service:8080/"



  }



}

O OrderClient trata o 404 do serviço devolvendo null, o que permite ao endpoint responder 404 ao app. Nos outros dois, uma falha vira HttpRequestException e cai no SafeAsync do exemplo 5.2.

Boas práticas e erros comuns em API Gateway e BFF

Boas práticas e pontos de atenção
Boas práticas e pontos de atenção

Erros comuns

  • Regra de negócio no BFF ou no Gateway: eles viram um "mini monólito". Regra fica nos serviços.
  • BFF único para todos os clientes: vira um God Gateway, acoplado a todas as telas.
  • Gateway como ponto único de falha: sem múltiplas instâncias e health checks, uma queda derruba tudo.
  • Chamadas sequenciais no BFF, somando latências.
  • Propagar exceção de um serviço secundário e quebrar a tela inteira.

Exemplo do erro das chamadas sequenciais, com a correção:

C#
// Evite: as latências se somam (tempo = pedido + rastreio)



var order = await orders.GetOrderAsync(id, ct);



var tracking = await trackingClient.GetTrackingAsync(id, ct);







// Prefira: as chamadas começam juntas (tempo = a mais lenta)



var orderTask = orders.GetOrderAsync(id, ct);



var trackingTask = trackingClient.GetTrackingAsync(id, ct);



await Task.WhenAll(orderTask, trackingTask);

Dicas de produção

  • Propague traceparent/correlation id do Gateway até os serviços (OpenTelemetry).
  • Valide o token no Gateway, mas repasse a identidade; serviços sensíveis ainda devem autorizar.
  • Defina timeouts por dependência, mais curtos para dados secundários.
  • Versione o contrato do BFF junto com o app (apps mobile antigos continuam em uso por meses).
  • Um BFF deve pertencer ao time que mantém o front que ele atende.
  • Cache de respostas agregadas quando o dado permitir (HybridCache no .NET 9+).

Timeout curto para o dado secundário, por dependência:

C#
builder.Services



    .AddHttpClient<INotificationClient, NotificationClient>(c =>



        c.BaseAddress = new Uri(services["Notification"]!))



    .AddStandardResilienceHandler(options =>



    {



        options.AttemptTimeout.Timeout =



            TimeSpan.FromSeconds(1);



        options.TotalRequestTimeout.Timeout =



            TimeSpan.FromSeconds(3);



        options.CircuitBreaker.SamplingDuration =



            TimeSpan.FromSeconds(2);



    });

O SamplingDuration do circuit breaker precisa ser pelo menos o dobro do AttemptTimeout; o handler recusa a configuração em caso contrário.

E o rastreio distribuído, para seguir uma requisição do app até o serviço:

C#
builder.Services.AddOpenTelemetry()



    .WithTracing(tracing => tracing



        .AddAspNetCoreInstrumentation()



        .AddHttpClientInstrumentation());

Ative o mesmo no Gateway, no BFF e nos serviços: o contexto de trace (traceparent) acompanha as chamadas HTTP de saída.

Específico do YARP

  • Mantenha o Gateway sem lógica de negócio: rotas, políticas e transforms. Transformação de corpo e agregação são do BFF.
  • Use Order nas rotas quando houver sobreposição de caminhos; menor valor tem prioridade.
  • Habilite health checks (ativo e passivo) antes de colocar mais de uma instância atrás do cluster.
  • Evite DangerousAcceptAnyServerCertificate fora de desenvolvimento.
  • Trate a configuração de rotas como código: versionada, revisada e com a mesma esteira de deploy. O reload dinâmico é conveniente, mas uma rota errada afeta todos os clientes.
  • Se o Gateway ficar atrás de outro proxy/load balancer, configure ForwardedHeaders para não perder o IP real do cliente (importante para o rate limit por IP).
  • Considere o Gateway como ponto crítico: múltiplas instâncias, limites de tamanho de corpo (MaxRequestBodySize) e timeouts por rota.

O health check ativo do cluster (seção 4.1.1) precisa de um endpoint de saúde no BFF:

C#
builder.Services.AddHealthChecks();







var app = builder.Build();



app.MapHealthChecks("/health");

Sem ele, a resposta não será 2xx e o YARP tira a destination do rodízio.

Quando usar API Gateway e BFF e quando evitar

Quando usar e quando evitar
Quando usar e quando evitar

Use quando

  • Há vários tipos de cliente com necessidades de dados diferentes.
  • Você já tem microsserviços e o cliente faz muitas chamadas para montar uma tela.
  • Quer centralizar autenticação, rate limiting e observabilidade.
  • Precisa escalar e evoluir cada experiência de forma independente.

Evite (ou adie) quando

  • Você tem um monólito e um só cliente: a camada extra só adiciona latência e deploy.
  • O time é pequeno e não consegue manter um BFF por cliente.
  • Os clientes consomem exatamente os mesmos dados: um Gateway simples basta.

Alternativas

  • Só Gateway: quando não há necessidade de adaptar payload por cliente.
  • GraphQL: quando os clientes precisam escolher os campos; troca-se o BFF por um schema flexível, com custo de complexidade próprio.
  • Composição no cliente: aceitável em apps pequenos, mas perde segurança e desempenho.

6.1. YARP vs NGINX: e o desempenho?

Uma dúvida comum é se vale trocar o YARP por um proxy dedicado como o NGINX. Um benchmark público comparou os dois com uma API mínima /hello e carga gerada pelo k6:

  • NGINX com configuração padrão: cerca de 10,2 mil requisições por segundo (RPS) e p90 de 21,23 ms. O YARP (cerca de 36,7 mil RPS e p90 de 7,77 ms) parecia vencer com folga.
  • NGINX ajustado: cerca de 46,9 mil RPS e p90 de 6,34 ms. Depois do ajuste, o NGINX passou o YARP em desempenho bruto.

O ajuste usado no teste foi este:

nginx
worker_processes auto;







events {



    worker_connections 65536;



    multi_accept on;



    use epoll;



}







upstream backend {



    keepalive 512;



}







# no bloco de proxy: proxy_http_version 1.1;

Como ler esse resultado

  • Configuração padrão distorce benchmark: só compare depois de ajustar os dois lados.
  • NGINX é a escolha para throughput máximo; YARP é a escolha para integração nativa com .NET (autenticação, políticas, extensão em C#, mesmo pipeline e mesmo deploy do time).
  • Os números vêm de um teste específico, com uma API sem lógica de negócio. Em um gateway real, o tempo dos serviços por trás costuma pesar mais que o do proxy. Meça no seu cenário, com seus payloads.

Conclusão: Gateway e BFF, cada um no seu papel

Conclusão: cada camada no seu papel
Conclusão: cada camada no seu papel

O API Gateway concentra o que é transversal (segurança, limites, roteamento, observabilidade) e o BFF concentra o que é específico de cada experiência (agregação e formato). Juntos, reduzem chamadas de rede, escondem os serviços internos e permitem que cada cliente evolua sem arrastar os outros.

Reflexão prática: comece pelo cliente que mais sofre com chamadas múltiplas, crie um BFF para ele e meça a redução de requisições e de latência antes de replicar o padrão. Não se trata de adicionar camadas, e sim de dar a cada cliente a experiência certa, mantendo o sistema seguro, escalável e simples de manter.

Referências sobre YARP e API Gateway em .NET

Leia também

Quer uma revisão da arquitetura do seu backend .NET? Conheça o serviço de backend e arquitetura .NET.

← todos os artigos