API Gateway vs Reverse Proxy
A distinção é de camada de abstração e consciência de domínio. Um reverse proxy é agnóstico ao conteúdo que trafega — ele encaminha bytes HTTP sem entender o que significam. Um API Gateway entende APIs: sabe que GET /orders/{id} pertence ao serviço de pedidos, que requer o scope orders:read, que a key pública de um usuário tem quota de 1000 req/dia, e que a resposta deve ter o campo customerId mascarado para clientes de certos países.
| Aspecto | Reverse Proxy | API Gateway |
|---|---|---|
| Camada | L4/L7 de rede | L7 de aplicação |
| Consciência de domínio | Nenhuma — encaminha bytes | Total — conhece rotas, métodos, schemas |
| Autenticação | Pode validar JWT via módulo | Nativa — OAuth, API Keys, mTLS, SAML |
| Rate limiting | Por IP ou URL | Por usuário, produto, endpoint, plano |
| Transformação | Headers apenas | Body, headers, query params, protocolos |
| Quotas | Não | Sim — daily/monthly, por produto |
| Portal do desenvolvedor | Não | Sim — registro, docs, sandbox |
| Monetização | Não | Sim — planos pagos, billing |
| Overhead de latência | ~0.1-0.5ms | ~1-10ms (validação de token, lookup de quota) |
| Exemplos | nginx, HAProxy, Caddy | Kong, AWS API GW, Azure APIM, Apigee |
Padrões de deployment
Edge Gateway — entrada única para APIs externas
O padrão mais comum: um único gateway na borda do sistema recebe todo o tráfego externo. Clientes (mobile, web, parceiros) acessam api.empresa.com e o gateway roteia para os serviços internos corretos. Centraliza autenticação, rate limiting e logging.
Internet
↓
api.empresa.com (DNS → Gateway)
↓ autenticação, rate limiting, logging
├→ /api/orders/* → orders-service:8080
├→ /api/products/* → products-service:8080
├→ /api/customers/* → customers-service:8080
└→ /api/payments/* → payments-service:8080 (PCI zone)
BFF (Backend for Frontend)
O padrão BFF cria um gateway especializado por tipo de cliente — em vez de um gateway genérico para todos. O BFF mobile agrega dados de forma otimizada para telas pequenas com conexão instável; o BFF web pode retornar payloads mais ricos; o BFF de parceiros expõe apenas as APIs com contrato estável.
mobile-app.empresa.com → BFF Mobile → orders-service
→ products-service (campos reduzidos)
→ push-notification-service
web-app.empresa.com → BFF Web → orders-service
→ products-service (campos completos)
→ recommendations-service
partner-api.empresa.com → BFF Partner → orders-service (apenas status)
→ products-service (apenas catálogo público)
A vantagem do BFF é que cada equipe (mobile, web, parceiros) tem autonomia para otimizar seu gateway sem afetar os outros. A desvantagem é duplicação de lógica — autenticação, logging e circuit breaking precisam ser implementados em cada BFF.
Internal Gateway — entre microsserviços
Para comunicação service-to-service que precisa de autenticação de serviço, rate limiting por chamador, e observabilidade fina — sem a sobrecarga de um service mesh. Menos comum porque service meshes (Istio, Linkerd) resolvem o mesmo problema de forma mais transparente.
Micro-gateway / Sidecar Gateway
Cada serviço tem seu próprio gateway sidecar que gerencia o tráfego de entrada — autenticação, rate limiting e transformação específicos para aquele serviço. Utilizado quando os requisitos de gateway são muito específicos por serviço para compartilhar uma instância central.
Autenticação e Autorização
O API Gateway é o local natural para validar identidade antes que a requisição chegue ao backend — os serviços internos recebem requisições já autenticadas e podem confiar nos headers injetados pelo gateway (user-id, roles, scopes).
API Keys
O mecanismo mais simples: um token opaco gerado pelo gateway, associado a um cliente (aplicação, parceiro). O gateway valida a key em cada requisição, aplica as quotas e permissões do plano associado, e injeta o identificador do cliente no header X-Consumer-ID para logging.
# Exemplo de fluxo com API Key
Cliente → GET /api/products HTTP/1.1
Authorization: ApiKey abc123xyz
Gateway:
1. Extrai "abc123xyz" do header Authorization
2. Lookup em Redis/banco: { consumer: "acme-corp", plan: "basic", quota_day: 1000, quota_used: 47 }
3. Verifica quota_used < quota_day
4. Incrementa quota_used atomicamente
5. Injeta headers para o backend:
X-Consumer-ID: acme-corp
X-Consumer-Plan: basic
X-Rate-Limit-Remaining: 953
6. Encaminha para products-service
Backend recebe requisição com X-Consumer-ID — não precisa validar auth
OAuth 2.0 e JWT
Para APIs que autenticam usuários finais — não apenas aplicações — OAuth 2.0 com JWTs é o padrão. O gateway valida o token JWT (assinatura, expiração, issuer, audience) sem consultar o authorization server a cada requisição — a chave pública do AS é cacheada localmente.
# Validação de JWT no gateway (Kong, por exemplo)
# Plugin jwt ou openid-connect
# Fluxo completo:
1. Cliente obtém token do Authorization Server (auth.empresa.com)
POST /oauth/token → { access_token: "eyJ...", expires_in: 3600 }
2. Cliente chama a API com o token
GET /api/orders Authorization: Bearer eyJ...
3. Gateway valida localmente:
- Verifica assinatura com chave pública JWKS (cacheada)
- Verifica exp (não expirado)
- Verifica iss = "auth.empresa.com"
- Verifica aud = "api.empresa.com"
- Extrai sub, scope, roles do payload
4. Gateway injeta no header para o backend:
X-User-ID: user-abc123
X-User-Roles: admin,orders:write
X-User-Scopes: orders:read orders:write
5. Backend confia nos headers — não revalida o token
mTLS para APIs de parceiros
Para APIs B2B onde o cliente é uma empresa (não um usuário), mTLS oferece autenticação mútua baseada em certificados — sem senhas ou tokens que podem vazar. O gateway valida o certificado cliente (client certificate) e o associa a um consumer configurado.
# Kong — plugin mTLS auth
plugins:
- name: mtls-auth
config:
ca_certificates:
- id: "uuid-do-certificado-CA-da-empresa-parceira"
skip_consumer_lookup: false
authenticated_group_by: "CN" # usar o Common Name do cert como consumer ID
Autorização — o gateway decide o quê, o backend decide o como
Uma separação saudável: o gateway verifica se o cliente tem permissão para chamar o endpoint (scope orders:write presente no token, plano inclui o recurso). O backend verifica se o usuário tem permissão para a operação específica naquele dado (o pedido pertence ao usuário?). Não delegue decisões de negócio ao gateway — ele não conhece o estado da aplicação.
Rate Limiting e Quotas
Rate limiting no gateway protege os backends de sobrecarga e garante uso justo entre clientes. A implementação correta requer escolher o algoritmo certo e o nível de granularidade.
Algoritmos
Fixed Window Counter
Conta requisições em janelas fixas de tempo (ex: 100 req/minuto). A janela reseta a cada minuto. Simples de implementar com Redis INCR + TTL. Problema: boundary burst — um cliente pode fazer 100 req nos últimos segundos de uma janela e 100 req nos primeiros da próxima, efetivamente passando 200 req em alguns segundos.
# Redis — fixed window
local key = "rate_limit:" .. consumer_id .. ":" .. math.floor(now / 60)
local count = redis.incr(key)
if count == 1 then
redis.expire(key, 60) -- TTL de 1 minuto
end
if count > 100 then
return 429 -- Too Many Requests
end
Sliding Window Log
Armazena o timestamp de cada requisição. Para verificar, conta quantas ocorreram na janela de 1 minuto terminando agora. Preciso — sem boundary burst — mas usa memória proporcional ao número de requisições por usuário.
Sliding Window Counter (aproximação)
Combina dois fixed windows para aproximar o sliding window: count = current_window_count + previous_window_count * (1 - elapsed/window_size). Eficiente em memória, boa aproximação sem armazenar timestamps individuais. É o algoritmo padrão do Redis Rate Limiting e do Kong.
Token Bucket
Um balde tem capacidade máxima de B tokens. Tokens são adicionados à taxa r por segundo. Cada requisição consome 1 token. Se o balde está vazio, a requisição é rejeitada (ou aguarda). Permite bursts até a capacidade do balde — cliente pode usar acumulado de tokens em um pico.
# Token Bucket — pseudocódigo
def check_rate_limit(consumer_id, capacity=100, refill_rate=10):
now = time.now()
bucket = redis.get(f"bucket:{consumer_id}") or {tokens: capacity, last_refill: now}
# Refill tokens baseado no tempo decorrido
elapsed = now - bucket.last_refill
new_tokens = min(capacity, bucket.tokens + elapsed * refill_rate)
if new_tokens < 1:
return False, 429 # balde vazio
redis.set(f"bucket:{consumer_id}", {tokens: new_tokens - 1, last_refill: now})
return True, None
Leaky Bucket
Requisições entram no balde a qualquer taxa, mas saem (são processadas) a uma taxa constante. Excess é rejeitado ou enfileirado. Garante taxa constante de processamento — útil para suavizar bursts antes de chegar ao backend. Menos comum em API Gateways modernos; mais usado em traffic shaping de rede.
Granularidade de rate limiting
O gateway deve suportar múltiplos eixos de limitação simultaneamente:
- Por IP: proteção básica contra clientes anônimos e ataques
- Por consumer (API Key / usuário): garantia de uso justo entre clientes autenticados
- Por rota: endpoints mais caros (relatórios, batch) têm limites menores
- Por plano: tier free tem 100 req/dia; tier pro tem 100k req/dia
- Por janela: por segundo (burst), por minuto, por hora, por dia, por mês
# Kong — plugin rate-limiting avançado
plugins:
- name: rate-limiting-advanced
config:
limit_by: consumer # limitar por consumer autenticado
strategy: sliding-window
limits:
second: 20 # burst: 20 req/s
minute: 500 # 500 req/min
hour: 5000 # 5000 req/h
day: 10000 # 10000 req/dia
sync_rate: 10 # sincronizar contadores com Redis a cada 10s
redis:
host: redis.internal
port: 6379
error_message: "Rate limit excedido. Tente novamente após {reset_at}."
hide_client_headers: false # retornar X-RateLimit-* headers ao cliente
Headers de rate limiting
# Headers de resposta padronizados (IETF draft-ietf-httpapi-ratelimit-headers)
X-RateLimit-Limit: 1000 # limite total na janela atual
X-RateLimit-Remaining: 847 # requisições restantes
X-RateLimit-Reset: 1715285400 # Unix timestamp do reset
Retry-After: 60 # segundos para aguardar (apenas quando 429)
# Quando limite é excedido:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715285400
{
"error": "rate_limit_exceeded",
"message": "Limite de 1000 requisições por hora excedido",
"retry_after_seconds": 60
}
Transformação de Payload
O gateway pode modificar requisições e respostas em trânsito — adicionando, removendo ou transformando campos, convertendo formatos, e adaptando contratos entre o que o cliente espera e o que o backend oferece.
Transformações comuns
# Kong — plugin request-transformer
plugins:
- name: request-transformer
config:
# Adicionar headers antes de encaminhar ao backend
add:
headers:
- "X-Consumer-ID:$(consumer.id)"
- "X-Request-ID:$(uuid)"
# Remover headers sensíveis do cliente
remove:
headers:
- "Authorization" # backend não precisa do token JWT
- "Cookie"
# Kong — plugin response-transformer
- name: response-transformer
config:
remove:
json:
- "internal_id" # não expor ID interno do banco
- "created_by_user_id" # não expor dado interno
add:
headers:
- "X-API-Version:v1"
# Transformação de formato (REST → SOAP, JSON → XML)
# Geralmente requer scripting Lua (Kong) ou policies (APIM, Apigee)
Protocol translation — REST para gRPC
Um gateway moderno pode expor uma API REST externamente e traduzir para gRPC internamente — chamado de transcoding. O Envoy suporta isso nativamente com anotações google.api.http no .proto. O AWS API Gateway e o Azure APIM têm suporte limitado; Kong requer plugin customizado.
# Envoy como gateway com gRPC transcoding
# O cliente chama REST, o Envoy chama gRPC no backend
# envoy.yaml
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/etc/envoy/api_descriptor.pb" # arquivo binário do proto
services:
- "orders.v1.OrderService"
print_options:
add_whitespace: false
always_print_primitive_fields: true
# Com isso:
# POST /v1/orders → gRPC OrderService.CreateOrder
# GET /v1/orders/{id} → gRPC OrderService.GetOrder
# (mapeamento via anotações google.api.http no .proto)
Request/Response validation
O gateway pode validar payloads contra um schema OpenAPI antes de encaminhar ao backend — rejeitando requisições malformadas com 400 antes de consumir recursos do serviço. Reduz carga nos backends e centraliza validação básica de formato.
# Kong — plugin request-validator
plugins:
- name: request-validator
config:
body_schema: |
{
"type": "object",
"required": ["customer_id", "items"],
"properties": {
"customer_id": { "type": "string", "format": "uuid" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["product_id", "quantity"],
"properties": {
"product_id": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 }
}
}
}
}
}
allowed_content_types:
application/json: true
version: draft4
Agregação de Backends
Em vez de o cliente fazer múltiplas requisições paralelas para diferentes serviços, o gateway pode agregar em uma única chamada — reduzindo latência total (especialmente crítico em redes móveis) e simplificando o cliente.
Parallel fan-out
# Cliente faz 1 request, gateway faz 3 em paralelo e combina
GET /api/dashboard → Gateway:
paralelo:
GET orders-service/api/recent-orders?user=123 → { orders: [...] }
GET recommendations-service/api/for-user?id=123 → { products: [...] }
GET notifications-service/api/unread?user=123 → { count: 5 }
combina:
→ {
recent_orders: [...],
recommendations: [...],
unread_notifications: 5
}
# Latência total = max(latências individuais), não soma
# Sem agregação: 80ms + 120ms + 60ms = 260ms
# Com agregação: max(80, 120, 60) = 120ms
Implementação de agregação
Gateways de mercado (Kong Enterprise, Apigee) suportam aggregation via scripts Lua ou JavaScript. Alternativas mais limpas:
- GraphQL como BFF: o cliente especifica exatamente o que precisa e o servidor (BFF GraphQL) agrega internamente via DataLoaders
- BFF dedicado em código: um microserviço pequeno implementado na linguagem do time, que agrega chamadas internas e expõe o endpoint combinado
- AWS Step Functions / Azure Logic Apps: orquestração gerenciada para fluxos mais complexos
Circuit Breaker e Retry no Gateway
O gateway pode implementar circuit breaker para proteger clientes quando backends estão degradados — em vez de deixar requisições pendentes por 30 segundos antes de timeout, o gateway responde rapidamente com 503 quando o backend está com falha.
# Envoy — circuit breaker por cluster
clusters:
- name: orders_cluster
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 100 # máx. conexões simultâneas
max_pending_requests: 50 # máx. requisições na fila
max_requests: 200 # máx. requisições simultâneas
max_retries: 3 # máx. retries simultâneos
# quando limites são excedidos, Envoy retorna 503 imediatamente
# Envoy — outlier detection (passive circuit breaker)
outlier_detection:
consecutive_5xx: 5 # 5 erros 5xx consecutivos → ejetar instância
interval: 10s
base_ejection_time: 30s # tempo mínimo fora do pool
max_ejection_percent: 50 # no máximo 50% do pool pode ser ejetado
# Retry policy
retry_policy:
retry_on: "5xx,reset,connect-failure,retriable-4xx"
num_retries: 2
per_try_timeout: 5s
retry_host_predicate:
- name: envoy.retry_host_predicates.previous_hosts # evitar host que já falhou
Retry only quando seguro
O gateway só deve fazer retry em operações idempotentes ou quando tem certeza de que o backend não processou a requisição (ex: falha de conexão antes de enviar). Retry cego em POST pode duplicar criações. Configure retry apenas para: GET/HEAD/OPTIONS (sempre idempotentes), conexões que falharam antes de chegar ao backend, e erros 503 com header Retry-After.
Portal do Desenvolvedor
Um API Gateway completo inclui um portal onde desenvolvedores externos podem descobrir APIs, gerar credenciais, testar endpoints em sandbox, e acompanhar uso e quotas. É a camada de produto da API — sem ela, uma API pública é apenas uma URL sem documentação.
Componentes de um portal maduro
- Catálogo de APIs: lista todas as APIs disponíveis com documentação interativa (OpenAPI/Swagger UI), exemplos de código gerados automaticamente, changelog e deprecations
- Self-service de credenciais: desenvolvedores criam aplicações e geram API Keys sem interação humana; o gateway cria o consumer e aplica o plano automaticamente
- Sandbox/Mock environment: ambiente isolado para testar sem impacto em produção; respostas simuladas para backends que ainda não existem
- Dashboard de uso: gráficos de requisições, erros, latência por endpoint e período; alertas quando quota está próxima do limite
- Planos e upgrade: fluxo de upgrade de plano com integração de billing (Stripe, etc.)
- Webhooks e notificações: alertas de rate limit, deprecation iminente, incidentes
Gestão do ciclo de vida da API
# Versionamento no gateway — múltiplas versões coexistindo
routes:
- name: orders-v1
paths: ["/api/v1/orders"]
service: orders-service-v1
plugins:
- name: response-transformer
config:
add.headers: ["Sunset: Sat, 31 Dec 2026 00:00:00 GMT",
"Deprecation: true",
"Link: </api/v2/orders>; rel=\"successor-version\""]
- name: orders-v2
paths: ["/api/v2/orders"]
service: orders-service-v2
# Quando v1 é deprecada:
# 1. Adicionar header Sunset com data de desativação
# 2. Logar consumidores ainda usando v1
# 3. Contactar consumidores com uso significativo
# 4. Desativar v1 na data de sunset
Soluções de Mercado
Kong Gateway (open source + enterprise)
Construído sobre nginx+OpenResty (Lua), é o API Gateway open source mais usado. A versão gratuita inclui roteamento, rate limiting, autenticação básica, plugins da comunidade. A versão Enterprise adiciona portal do desenvolvedor, RBAC granular, analytics avançado e suporte comercial. Configurável via Admin API REST, Deck (declarativo como código) ou KIC (Kubernetes Ingress Controller).
# Kong declarativo com Deck (deck.yaml)
services:
- name: orders-service
url: http://orders-service:8080
routes:
- name: orders-route
paths: ["/api/v1/orders"]
methods: [GET, POST, PATCH, DELETE]
strip_path: false
plugins:
- name: jwt
config:
secret_is_base64: false
claims_to_verify: [exp, nbf]
- name: rate-limiting
config:
minute: 1000
hour: 10000
policy: redis
redis_host: redis.internal
- name: correlation-id
config:
header_name: X-Request-ID
generator: uuid#counter
- name: prometheus # métricas para Grafana/Prometheus
config:
per_consumer: true
AWS API Gateway
Serviço gerenciado da AWS — sem infraestrutura para operar. Dois tipos: REST API (mais completo, mais caro) e HTTP API (mais simples, mais barato, menor latência). Integra nativamente com Lambda, ECS, EKS, Cognito, WAF e CloudWatch. Rate limiting e throttling configurados via usage plans.
# AWS API Gateway via CDK (TypeScript)
const api = new RestApi(this, 'OrdersApi', {
restApiName: 'Orders Service',
defaultCorsPreflightOptions: {
allowOrigins: Cors.ALL_ORIGINS,
allowMethods: ['GET', 'POST', 'PATCH'],
},
});
// Authorizer JWT com Cognito
const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
cognitoUserPools: [userPool],
});
// Rota com authorizer e integração Lambda
const orders = api.root.addResource('orders');
orders.addMethod('POST', new LambdaIntegration(createOrderFn), {
authorizer,
authorizationType: AuthorizationType.COGNITO,
});
// Usage plan — rate limiting e quotas
const plan = api.addUsagePlan('BasicPlan', {
throttle: { rateLimit: 100, burstLimit: 200 },
quota: { limit: 10000, period: Period.DAY },
});
plan.addApiStage({ stage: api.deploymentStage });
// API Key para acesso
const key = api.addApiKey('PartnerKey');
plan.addApiKey(key);
Azure API Management (APIM)
Solução enterprise da Microsoft com portal do desenvolvedor completo, políticas XML expressivas, integração com Azure AD e RBAC, suporte a mocking, e analytics nativo. Curva de aprendizado maior que Kong, mas integração excepcional com o ecossistema Azure.
<!-- Azure APIM — policies XML -->
<policies>
<inbound>
<!-- Validar JWT do Azure AD -->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration"/>
<audiences><audience>api://orders-api</audience></audiences>
<required-claims>
<claim name="roles" match="any">
<value>Orders.Read</value>
<value>Orders.Write</value>
</claim>
</required-claims>
</validate-jwt>
<!-- Rate limiting por subscription key -->
<rate-limit-by-key calls="100" renewal-period="60"
counter-key="@(context.Subscription.Id)" />
<!-- Injetar correlation ID -->
<set-header name="X-Request-ID" exists-action="skip">
<value>@(Guid.NewGuid().ToString())</value>
</set-header>
</inbound>
<backend>
<retry condition="@(context.Response.StatusCode == 503)" count="2" interval="1">
<forward-request timeout="30" />
</retry>
</backend>
<outbound>
<!-- Remover headers internos da resposta -->
<set-header name="X-Internal-Server" exists-action="delete"/>
<!-- Adicionar rate limit headers -->
<set-header name="X-RateLimit-Remaining">
<value>@(context.Response.Headers.GetValueOrDefault("X-RateLimit-Remaining","")</value>
</set-header>
</outbound>
</policies>
Comparativo de soluções
| Solução | Modelo | Melhor para | Cuidados |
|---|---|---|---|
| Kong OSS | Self-hosted, open source | Controle total, budget limitado, muita customização | Operação própria, HA manual |
| Kong Enterprise | Self-hosted ou Cloud | Portal completo, RBAC, suporte comercial | Custo de licença elevado |
| AWS API GW | Serverless SaaS | AWS-native, Lambda, sem ops | Vendor lock-in, custo por requisição |
| Azure APIM | SaaS (PaaS) | Azure AD, enterprise, portal robusto | Provisionamento lento, custo alto |
| Apigee (Google) | SaaS | APIs públicas, analytics avançado, monetização | Complexidade, custo, Google Cloud dependência |
| Traefik | Self-hosted, open source | Kubernetes-native, discovery automático, simplicidade | Portal e analytics limitados vs Kong |
API Gateway vs Service Mesh
Uma confusão comum: API Gateway e Service Mesh parecem resolver problemas similares (observabilidade, autenticação, retry), mas operam em camadas e escopos diferentes.
| Aspecto | API Gateway | Service Mesh |
|---|---|---|
| Tráfego | Norte-Sul (externo → interno) | Leste-Oeste (serviço → serviço) |
| Audiência | Clientes externos, parceiros | Serviços internos |
| Autenticação | API Keys, OAuth, JWT de usuários | mTLS entre serviços, SPIFFE/SPIRE |
| Rate limiting | Por consumidor, por plano | Por serviço chamador |
| Deployment | Centralizado (1 instância/cluster) | Descentralizado (sidecar em cada pod) |
| Protocolo | HTTP/REST, GraphQL, gRPC (via transcoding) | Qualquer TCP — gRPC, HTTP, banco de dados |
| Complexidade ops | Moderada | Alta (plano de controle, sidecars, certificados) |
Em arquiteturas maduras, ambos coexistem: o API Gateway gerencia a entrada do tráfego externo, e o service mesh gerencia a comunicação interna entre serviços. O próximo conceito explora service meshes em profundidade.
Comparação por linguagem
Implementação de um BFF gateway leve em código — o padrão alternativo a soluções de mercado quando a lógica de agregação é complexa demais para configuração declarativa.
// YARP é a biblioteca Microsoft para reverse proxy / gateway em .NET
// Permite roteamento, transformação e autenticação em código C#
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(transforms =>
{
// Transformação customizada — injeta X-Consumer-ID após validar JWT
transforms.AddRequestTransform(async ctx =>
{
var user = ctx.HttpContext.User;
if (user.Identity?.IsAuthenticated == true)
{
var userId = user.FindFirst("sub")?.Value;
ctx.ProxyRequest.Headers.TryAddWithoutValidation(
"X-Consumer-ID", userId);
// Remover Authorization antes de encaminhar ao backend
ctx.ProxyRequest.Headers.Remove("Authorization");
}
});
// Adicionar correlation ID
transforms.AddRequestTransform(ctx =>
{
var requestId = ctx.HttpContext.TraceIdentifier;
ctx.ProxyRequest.Headers.TryAddWithoutValidation(
"X-Request-ID", requestId);
return ValueTask.CompletedTask;
});
});
// Rate limiting com ASP.NET Core
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("per-user", context =>
{
var userId = context.User.FindFirst("sub")?.Value ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
return RateLimitPartition.GetSlidingWindowLimiter(userId, _ =>
new SlidingWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 4,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 10,
});
});
options.OnRejected = async (context, token) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsJsonAsync(new
{
error = "rate_limit_exceeded",
retry_after_seconds = 60
}, token);
};
});
// BFF endpoint de agregação — combina múltiplos serviços
builder.Services.AddHttpClient("orders", c => c.BaseAddress = new Uri("http://orders-service"));
builder.Services.AddHttpClient("products", c => c.BaseAddress = new Uri("http://products-service"));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
// Endpoint de agregação customizado
app.MapGet("/api/dashboard", async (
IHttpClientFactory factory,
HttpContext ctx) =>
{
var userId = ctx.User.FindFirst("sub")?.Value!;
var ordersClient = factory.CreateClient("orders");
var productsClient = factory.CreateClient("products");
// Fan-out paralelo
var ordersTask = ordersClient.GetFromJsonAsync<OrdersResponse>(
$"/internal/orders/recent?userId={userId}",
ctx.RequestAborted);
var recommendationsTask = productsClient.GetFromJsonAsync<RecommendationsResponse>(
$"/internal/recommendations?userId={userId}",
ctx.RequestAborted);
await Task.WhenAll(ordersTask, recommendationsTask);
return Results.Ok(new
{
recent_orders = ordersTask.Result?.Orders,
recommendations = recommendationsTask.Result?.Products,
});
})
.RequireAuthorization()
.RequireRateLimiting("per-user");
// Proxy YARP para rotas genéricas
app.MapReverseProxy();
app.Run();
// appsettings.json — configuração do YARP
// {
// "ReverseProxy": {
// "Routes": {
// "orders-route": {
// "ClusterId": "orders-cluster",
// "Match": { "Path": "/api/v1/orders/{**catch-all}" },
// "AuthorizationPolicy": "default"
// }
// },
// "Clusters": {
// "orders-cluster": {
// "Destinations": {
// "orders-1": { "Address": "http://orders-1:8080" },
// "orders-2": { "Address": "http://orders-2:8080" }
// },
// "LoadBalancingPolicy": "LeastRequests",
// "HealthCheck": { "Active": { "Enabled": true, "Path": "/ready" } }
// }
// }
// }
// }
YARP é o proxy reverso oficial da Microsoft para ASP.NET Core — transforma qualquer aplicação .NET em um gateway programável com toda a expressividade do C# para transformações e lógica de roteamento complexa.
import asyncio
import httpx
from fastapi import FastAPI, Depends, HTTPException, Request, Response
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
app = FastAPI(title="BFF Gateway")
# Rate limiting com slowapi (baseado em limits)
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Clientes HTTP reutilizáveis (connection pool)
orders_client = httpx.AsyncClient(
base_url="http://orders-service",
timeout=10.0,
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
)
products_client = httpx.AsyncClient(
base_url="http://products-service",
timeout=10.0,
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
JWKS_URL = "https://auth.empresa.com/.well-known/jwks.json"
_jwks_cache = None
async def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
try:
# Validar JWT localmente com chave pública cacheada
payload = jwt.decode(
token,
await get_jwks(),
algorithms=["RS256"],
audience="api.empresa.com",
)
return payload
except JWTError as e:
raise HTTPException(status_code=401, detail=f"Token inválido: {e}")
# Middleware — injetar correlation ID e logar requisições
@app.middleware("http")
async def observability_middleware(request: Request, call_next):
import uuid
request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
request.state.request_id = request_id
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
# Endpoint de agregação — dashboard
@app.get("/api/dashboard")
@limiter.limit("60/minute") # rate limit por IP (substituir por user ID em prod)
async def get_dashboard(
request: Request,
user: dict = Depends(get_current_user),
):
user_id = user["sub"]
# Fan-out paralelo para múltiplos serviços
orders_task = orders_client.get(
f"/internal/orders/recent",
params={"user_id": user_id},
headers={"X-Consumer-ID": user_id},
)
recommendations_task = products_client.get(
f"/internal/recommendations",
params={"user_id": user_id},
headers={"X-Consumer-ID": user_id},
)
orders_resp, recs_resp = await asyncio.gather(
orders_task,
recommendations_task,
return_exceptions=True, # não propaga exceção de um para o outro
)
# Tratamento de falha parcial — degradar graciosamente
orders = []
if isinstance(orders_resp, httpx.Response) and orders_resp.status_code == 200:
orders = orders_resp.json().get("orders", [])
recommendations = []
if isinstance(recs_resp, httpx.Response) and recs_resp.status_code == 200:
recommendations = recs_resp.json().get("products", [])
return {
"user_id": user_id,
"recent_orders": orders,
"recommendations": recommendations,
}
# Proxy genérico para outros endpoints — encaminha com autenticação validada
@app.api_route(
"/api/v1/orders/{path:path}",
methods=["GET", "POST", "PATCH", "DELETE"]
)
@limiter.limit("100/minute")
async def proxy_orders(
request: Request,
path: str,
user: dict = Depends(get_current_user),
):
# Encaminhar para o backend com consumer ID injetado
upstream_url = f"/v1/orders/{path}"
body = await request.body()
upstream_response = await orders_client.request(
method=request.method,
url=upstream_url,
content=body,
headers={
"Content-Type": request.headers.get("Content-Type", "application/json"),
"X-Consumer-ID": user["sub"],
"X-Consumer-Roles": ",".join(user.get("roles", [])),
},
params=dict(request.query_params),
)
return Response(
content=upstream_response.content,
status_code=upstream_response.status_code,
headers=dict(upstream_response.headers),
)
@app.on_event("shutdown")
async def shutdown():
await orders_client.aclose()
await products_client.aclose()
FastAPI como BFF gateway usa asyncio.gather para fan-out paralelo e return_exceptions=True para degradação graciosa — falha de um serviço não cancela os outros.
// gateway/main.go
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/time/rate"
)
type Gateway struct {
ordersProxy *httputil.ReverseProxy
productsProxy *httputil.ReverseProxy
ordersClient *http.Client
limiterMap sync.Map // consumer_id → *rate.Limiter
jwksCache *JWKSCache
}
func NewGateway(ordersURL, productsURL string) *Gateway {
ordersTarget, _ := url.Parse(ordersURL)
productsTarget, _ := url.Parse(productsURL)
transport := &http.Transport{
MaxIdleConns: 200,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
}
ordersProxy := httputil.NewSingleHostReverseProxy(ordersTarget)
ordersProxy.Transport = transport
// Modificar request antes de encaminhar
ordersProxy.Director = func(req *http.Request) {
req.URL.Scheme = ordersTarget.Scheme
req.URL.Host = ordersTarget.Host
req.Host = ordersTarget.Host
req.Header.Del("Authorization") // não encaminhar token JWT ao backend
}
return &Gateway{
ordersProxy: ordersProxy,
ordersClient: &http.Client{Transport: transport, Timeout: 10 * time.Second},
jwksCache: NewJWKSCache("https://auth.empresa.com/.well-known/jwks.json"),
}
}
// Middleware de autenticação JWT
func (g *Gateway) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenStr := extractBearerToken(r)
if tokenStr == "" {
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "token ausente"})
return
}
claims, err := g.jwksCache.ValidateToken(tokenStr)
if err != nil {
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "token inválido"})
return
}
// Injetar claims no contexto para handlers
ctx := context.WithValue(r.Context(), claimsKey, claims)
r = r.WithContext(ctx)
// Injetar header para backends internos
r.Header.Set("X-Consumer-ID", claims.Subject)
next.ServeHTTP(w, r)
})
}
// Middleware de rate limiting por consumer (token bucket)
func (g *Gateway) RateLimitMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value(claimsKey).(*Claims)
consumerID := claims.Subject
limiter := g.getLimiter(consumerID)
if !limiter.Allow() {
w.Header().Set("Retry-After", "60")
writeJSON(w, http.StatusTooManyRequests, map[string]string{
"error": "rate_limit_exceeded",
"message": "Limite de requisições excedido",
})
return
}
next.ServeHTTP(w, r)
})
}
func (g *Gateway) getLimiter(consumerID string) *rate.Limiter {
// Token bucket: 100 req/min = ~1.67 req/s, burst de 20
if v, ok := g.limiterMap.Load(consumerID); ok {
return v.(*rate.Limiter)
}
l := rate.NewLimiter(rate.Every(600*time.Millisecond), 20) // ~100/min, burst 20
g.limiterMap.Store(consumerID, l)
return l
}
// Handler de agregação — fan-out paralelo
func (g *Gateway) DashboardHandler(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value(claimsKey).(*Claims)
userID := claims.Subject
type result struct {
data interface{}
err error
}
ordersCh := make(chan result, 1)
recsCh := make(chan result, 1)
// Fan-out paralelo
go func() {
resp, err := g.ordersClient.Get(
"http://orders-service/internal/orders/recent?user_id=" + userID)
if err != nil {
ordersCh <- result{err: err}
return
}
defer resp.Body.Close()
var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
ordersCh <- result{data: data["orders"]}
}()
go func() {
resp, err := g.ordersClient.Get(
"http://products-service/internal/recommendations?user_id=" + userID)
if err != nil {
recsCh <- result{err: err}
return
}
defer resp.Body.Close()
var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
recsCh <- result{data: data["products"]}
}()
// Coletar com timeout
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
var orders, recommendations interface{}
for i := 0; i < 2; i++ {
select {
case res := <-ordersCh:
if res.err == nil {
orders = res.data
}
case res := <-recsCh:
if res.err == nil {
recommendations = res.data
}
case <-ctx.Done():
// timeout — retornar o que já temos
}
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"user_id": userID,
"recent_orders": orders,
"recommendations": recommendations,
})
}
func main() {
gw := NewGateway(
"http://orders-service:8080",
"http://products-service:8080",
)
mux := http.NewServeMux()
mux.HandleFunc("/api/dashboard", gw.DashboardHandler)
mux.Handle("/api/v1/orders/", gw.ordersProxy)
// Chain de middlewares: Auth → RateLimit → Handler
handler := gw.AuthMiddleware(gw.RateLimitMiddleware(mux))
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 5 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 90 * time.Second,
}
server.ListenAndServe()
}
Go usa goroutines e channels para fan-out paralelo com controle de timeout via context.WithTimeout — o select coleta resultados à medida que chegam e retorna o que tiver ao expirar o timeout, garantindo degradação graciosa.
Decisões de engenharia
Exercícios práticos
- Instale o Kong localmente via Docker e configure um serviço para o seu backend local. Adicione os plugins:
jwt(validação de token),rate-limiting(100 req/min por consumer),correlation-ideprometheus. Usedeck dumppara exportar a configuração declarativa. Critério:curlsem token retorna 401 com{"message": "Unauthorized"}; request com token válido passa; a 101ª request no minuto retorna 429 comRetry-Afterheader;deck syncrestaura a configuração completa a partir do YAML sem erros. - Implemente um BFF de agregação que combine dados de dois backends em uma única resposta, com tratamento de falha parcial: se um backend retornar 5xx, retorne os dados do que funcionou com
partial: true. Critério: com ambos os backends funcionando, latência total ≤ max(latências individuais) + 10ms epartial: false; ao derrubar um backend, resposta em ≤ timeout_configurado compartial: truee dados do backend funcional; ambos os backends com 5xx retornapartial: truecom listas vazias (não 500). - Implemente os quatro algoritmos de rate limiting (fixed window, sliding window counter, token bucket, leaky bucket) em memória. Teste o boundary: envie 100 requests nos últimos 500ms de uma janela e 100 nos primeiros 500ms da próxima. Critério: fixed window permite burst de 200 na fronteira; sliding window bloqueia o burst após ~100 requests em qualquer janela de 1 minuto; token bucket permite burst até o capacity configurado antes de restringir; leaky bucket processa a taxa constante independente do padrão de chegada.
- Configure AWS API Gateway (ou Azure APIM) com duas usage plans: basic (100 req/min, 1k req/dia) e pro (1000 req/min, 100k req/dia). Crie uma API Key para cada plano. Critério: a key basic recebe 429 após 100 requests em 1 minuto enquanto a key pro continua funcionando; ambas recebem 429 após esgotarem a quota diária; headers
X-RateLimit-Remainingsão retornados em cada resposta com valor decrementando. - Implemente versionamento de API no gateway: exponha
/api/v1/orders(backend legado) e/api/v2/orders(backend novo) simultaneamente. Critério: todas as respostas de/api/v1/ordersincluem headersSunset: <data-futura>eDeprecation: true; logs estruturados em JSON incluem camposconsumer_ideapi_versionem cada request, permitindo query de "consumers ainda em v1";/api/v2/ordersresponde sem headers Sunset.
Perguntas de entrevista
Qual a diferença fundamental entre API Gateway e reverse proxy? Quando cada um é apropriado?
Um reverse proxy é agnóstico ao conteúdo — ele encaminha bytes HTTP baseado em URL e aplica políticas de rede (TLS, load balancing, compressão, health checks). Ele não sabe que POST /orders é uma operação de negócio, que requer o scope orders:write, ou que o consumer "Acme Corp" tem quota de 1000 req/dia.
Um API Gateway entende APIs como produto: sabe que cada rota tem um contrato (schema OpenAPI), que consumers têm planos diferentes com quotas distintas, que tokens JWT carregam scopes que determinam acesso por endpoint, e que a resposta deve excluir campos internos antes de chegar ao cliente externo. Ele é o ponto de controle de negócio da API.
Use reverse proxy quando: comunicação interna entre serviços sem semântica de produto, serving de assets estáticos com proxy simples, ou quando as funcionalidades de gateway seriam configuradas via código (YARP, FastAPI) em vez de produto. Use API Gateway quando: API pública ou semi-pública com consumers externos, necessidade de portal do desenvolvedor e self-service de credenciais, múltiplos planos de preços, ou necessidade de analytics de uso por endpoint e consumer.
Como implementar rate limiting correto em múltiplas instâncias do gateway sem estado local?
Rate limiting com estado local (em memória de cada instância) falha em escala horizontal: 3 instâncias com limite de 100 req/min permitem 300 req/min efetivos se o load balancer distribui uniformemente. A solução é estado centralizado.
O padrão mais comum usa Redis como contador atômico compartilhado. Para sliding window counter: a instância do gateway incrementa uma chave Redis com INCRBY atômico e TTL, e o valor retornado é o total atual de todas as instâncias. Para token bucket: a instância lê o estado do bucket (tokens, last_refill), calcula o refill baseado no tempo decorrido, e usa uma transação Redis (MULTI/EXEC ou Lua script) para atualizar atomicamente — evitando race condition onde duas instâncias leem o mesmo valor e ambas consomem o mesmo token.
Overhead de latência: cada request requer 1-2 round-trips ao Redis (~0.5-2ms em rede local). Para throughput muito alto (>10k req/s), o Redis pode se tornar gargalo. Mitigações: Redis cluster para escalar, ou "approximate rate limiting" — cada instância sincroniza contadores com Redis periodicamente (Kong's sync_rate) em vez de por request, aceitando que o limite pode ser ultrapassado ligeiramente entre sincronizações.
Explique o padrão BFF (Backend for Frontend) — quando os benefícios superam os custos?
O padrão BFF cria um servidor dedicado por tipo de cliente (mobile, web, parceiro) que agrega chamadas a microsserviços backend e entrega exatamente o que aquele cliente precisa — sem over-fetching (web pedindo campos que mobile não usa) ou under-fetching (mobile fazendo múltiplas chamadas para montar uma tela).
Os benefícios são: (1) cada equipe de frontend controla seu próprio BFF, iterando sem depender do time de backend para criar novos endpoints; (2) o BFF pode agregar, transformar e adaptar contratos sem modificar os serviços internos; (3) payloads otimizados por cliente (mobile recebe 3 campos, web recebe 15); (4) contrato estável para parceiros externos enquanto os serviços internos evoluem livremente.
Os custos são: duplicação de lógica transversal (autenticação, logging, circuit breaking) em cada BFF; mais serviços para operar e monitorar; possível inconsistência entre BFFs se a lógica de negócio for duplicada em vez de centralizada. Os benefícios superam os custos quando: há pelo menos 2 tipos de clientes com necessidades genuinamente diferentes, as equipes de frontend têm autonomia para deployar, e o volume de chamadas ao backend por tela justifica a agregação. Para sistemas com um único tipo de cliente e API estável, um gateway centralizado é mais simples.
Como o padrão de circuit breaker no gateway protege os backends e evita falha em cascata?
Sem circuit breaker, quando um backend fica lento, as requisições ao gateway se acumulam aguardando timeout — esgotando threads/goroutines do gateway, que começa a recusar requisições de outros backends também saudáveis (falha em cascata). O circuit breaker interrompe esse ciclo.
O circuit breaker tem três estados: Closed (operação normal, todas as requisições passam), Open (backend com falha detectada — requisições são rejeitadas imediatamente com 503 sem tentar o backend), Half-Open (após o timeout, permite um número limitado de requisições de teste — se passarem, fecha o circuito; se falharem, abre novamente).
No Envoy, o circuit breaker (outlier detection) é passivo: após N erros 5xx consecutivos de uma instância, ela é ejetada do pool por um tempo base exponencial — sem estado de "aberto/fechado" global, mas por instância individual. Isso é mais granular: se 1 de 3 instâncias está com problema, apenas ela é ejetada, não todo o cluster. Para circuit breaking no nível do cluster (todos com problema), o max_requests e max_pending_requests limitam a concorrência total e fazem o Envoy retornar 503 quando os limites são ultrapassados.
Retry só deve ser configurado com backoff exponencial e jitter para evitar thundering herd (todos retentando ao mesmo tempo ao voltar o backend), e apenas em operações idempotentes (GET, HEAD) ou em erros de conexão antes de enviar o request.
Como versionar APIs via gateway sem breaking change para consumers existentes?
O gateway permite manter múltiplas versões coexistindo com zero downtime para consumers. As estratégias de versionamento via URL (/v1/, /v2/) são as mais claras para consumers — cada versão é uma rota separada no gateway que aponta para o backend correto (ou para o mesmo backend com transformação de payload).
O ciclo de vida correto: (1) ao lançar v2, manter v1 operacional; (2) adicionar headers Sunset: <data> e Deprecation: true em todas as respostas de v1 — isso é padronizado no RFC 8594 e ferramentas como Insomnia e Postman alertam automaticamente; (3) monitorar consumers ainda em v1 via logs (agregar por consumer_id × api_version); (4) contatar consumers com uso significativo de v1 para oferecer suporte à migração; (5) na data de sunset, retornar 410 Gone com link para v2.
Estratégia alternativa para mudanças compatíveis: em vez de nova versão, adicionar campos opcionais ao schema existente (sem remover campos antigos). O gateway pode aplicar transformações de resposta para remover campos novos de clients que declaram versão antiga via header Accept-Version. Isso evita proliferação de versões para mudanças aditivas — reserve v2, v3 para mudanças breaking (remoção de campo, mudança de tipo, renomeação de rota).
Referências
- docs Kong Gateway Documentation — Kong Inc.
- docs Amazon API Gateway Developer Guide — AWS.
- docs Azure API Management Documentation — Microsoft.
- docs YARP — Yet Another Reverse Proxy — Microsoft.
- livro Microservices Patterns — Chris Richardson (Manning, 2018).
- livro Building Microservices — Sam Newman (2ª ed., O'Reilly, 2021).
- artigo Pattern: Backends for Frontends — Sam Newman (2015).
- artigo Gateway Aggregation pattern — Microsoft Architecture Center.
- standard RFC 6749 — The OAuth 2.0 Authorization Framework — IETF (2012).
- standard RFC 8594 — The Sunset HTTP Header Field — IETF (2019).
- standard draft-ietf-httpapi-ratelimit-headers — IETF.
- artigo An Introduction to API Gateways — Fielding, Roy T. — Architectural Styles and Design (2000).