Arquitetura APIs .NET GraphQL Decisões Técnicas

REST vs GraphQL vs gRPC: guia de decisão honesto para APIs em 2026

Quando usar REST, GraphQL ou gRPC em projetos .NET: trade-offs reais de cada abordagem, casos de uso ideais e os erros mais comuns de quem adota cada tecnologia sem pensar no contexto.

N
Neryx Digital Architects
15 de março de 2026
12 min de leitura
210 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

A pergunta errada e a pergunta certa

Toda semana alguém pergunta: "REST ou GraphQL?" Essa é a pergunta errada. A pergunta certa é: "Quem vai consumir essa API, com que frequência, com que padrão de acesso, e quais são os custos de manutenção que o time consegue absorver?"

REST, GraphQL e gRPC resolvem problemas diferentes. Cada um tem um custo de implementação, um custo de operação e um conjunto de problemas que resolve bem — e problemas que complica. Este guia é sobre entender esses trade-offs, não sobre escolher o mais "moderno".

REST: o padrão que sobrevive há 25 anos por uma razão

REST (Representational State Transfer) é o default por uma razão simples: é o modelo mais amplamente suportado, mais fácil de depurar e mais fácil de entender para qualquer desenvolvedor.

O que REST faz bem

Recursos bem definidos com URLs semânticas. Verbos HTTP com semântica clara (GET, POST, PUT, PATCH, DELETE). Cache nativo via HTTP headers. Tooling excelente (Swagger/OpenAPI, Postman, navegador como client). Stateless por design. Todo desenvolvedor conhece.

// REST endpoint bem projetado no .NET
app.MapGet("/clientes/{id:guid}/pedidos", async (
    Guid id,
    [FromQuery] int pagina = 1,
    [FromQuery] int tamanho = 20,
    [FromQuery] string? status,
    ISender sender,
    CancellationToken ct) =>
{
    var query = new GetPedidosClienteQuery(id, pagina, tamanho, status);
    var resultado = await sender.Send(query, ct);
    return resultado.IsEmpty
        ? Results.NotFound()
        : Results.Ok(resultado);
})
.WithName("GetPedidosCliente")
.WithOpenApi()
.Produces<PagedResult<PedidoSummaryDto>>()
.Produces(404);

Onde REST tem fricção

Over-fetching: você precisa de nome e foto do cliente, mas o endpoint retorna 30 campos. Solução: campos de projeção (?fields=nome,foto) — funciona mas é não-padrão.

Under-fetching: para montar a tela do painel do usuário você precisa de 5 endpoints diferentes. N+1 de endpoints. Solução: endpoints de composição (/dashboard) — cria acoplamento frontend-backend.

Versionamento: /v1/pedidos, /v2/pedidos — versão na URL é simples mas gera código duplicado. Header versioning (Accept: application/vnd.api+json;version=2) é mais limpo mas menos visível.

Quando REST é a escolha certa

  • APIs públicas ou para parceiros externos — todo mundo sabe consumir REST
  • APIs simples com recursos bem definidos e relacionamentos claros
  • Quando caching de HTTP é importante (GET de recursos públicos)
  • Times sem experiência prévia com GraphQL ou gRPC
  • Integrações com sistemas legados ou externos que esperam REST

GraphQL: poder para o frontend, custo para o backend

GraphQL foi criado pelo Facebook para resolver um problema específico: um grafo de dados complexo com múltiplos tipos de clientes (mobile, web, parceiros) com necessidades de dados completamente diferentes.

O que GraphQL faz bem

O cliente declara exatamente os dados que precisa — nem mais, nem menos. Uma única requisição pode buscar dados de múltiplos recursos. Tipagem forte com schema auto-documentado. Evolução de API sem versioning (adicionar campos é non-breaking).

# Cliente pede exatamente o que precisa
query PainelCliente($clienteId: ID!) {
  cliente(id: $clienteId) {
    nome
    foto
    pedidos(ultimosN: 5) {
      id
      status
      valorTotal
      dataConfirmacao
      itens {
        produto { nome }
        quantidade
      }
    }
    creditoDisponivel
  }
}

Uma requisição. Dados exatos. Sem over-fetching, sem under-fetching.

Setup no .NET com Hot Chocolate

builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddMutationType<Mutation>()
    .AddProjections()
    .AddFiltering()
    .AddSorting()
    .AddAuthorization();

// Query type
public class Query
{
    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public IQueryable<Cliente> GetClientes([Service] AppDbContext db) =>
        db.Clientes.AsNoTracking();

    public async Task<Cliente?> GetCliente(
        Guid id,
        [Service] IClienteRepository repo,
        CancellationToken ct) =>
        await repo.GetByIdAsync(id, ct);
}

O que os tutoriais escondem sobre GraphQL

N+1 problem: uma query que pede 100 clientes e seus pedidos pode disparar 101 queries no banco. Requer DataLoader para resolver:

public class PedidosByClienteDataLoader : BatchDataLoader<Guid, List<Pedido>>
{
    private readonly IDbContextFactory<AppDbContext> _dbFactory;

    protected override async Task<IReadOnlyDictionary<Guid, List<Pedido>>>
        LoadBatchAsync(IReadOnlyList<Guid> clienteIds, CancellationToken ct)
    {
        await using var db = await _dbFactory.CreateDbContextAsync(ct);
        return await db.Pedidos
            .Where(p => clienteIds.Contains(p.ClienteId))
            .GroupBy(p => p.ClienteId)
            .ToDictionaryAsync(g => g.Key, g => g.ToList(), ct);
    }
}

Queries maliciosas: um cliente pode pedir dados circulares ou queries extremamente profundas, sobrecarregando o servidor. Precisa de depth limiting e complexity analysis:

builder.Services
    .AddGraphQLServer()
    .AddMaxExecutionDepthRule(maxAllowedExecutionDepth: 5)
    .AddOperationComplexityAnalyzer(o => o.MaximumAllowed = 1000);

Caching HTTP não funciona: toda query GraphQL é um POST. Sem cache nativo de HTTP. Requer caching na camada de aplicação ou Apollo Client no frontend.

Curva de aprendizado real: DataLoader, resolvers, fragments, subscriptions, federation — o ecosistema é poderoso mas tem profundidade.

Quando GraphQL faz sentido

  • API consumida por múltiplos clientes com necessidades de dados muito diferentes (app mobile, web, TV)
  • Grafo de dados complexo com muitos relacionamentos
  • Frontend tem autonomia para evoluir sem mudanças de backend
  • Time de frontend maduro que vai usar as capacidades avançadas
  • Sistema de BFF (Backend for Frontend) que agrega múltiplos serviços

gRPC: performance máxima para comunicação interna

gRPC usa Protobuf (Protocol Buffers) para serialização binária e HTTP/2 para transporte. É tipicamente 5-10x mais rápido que REST em throughput, com payloads menores.

O que gRPC faz bem

Serialização binária eficiente. Streaming bidirecional nativo (HTTP/2). Contratos tipados via .proto (geração de código em qualquer linguagem). Perfeito para comunicação entre microsserviços onde performance importa.

// pedidos.proto
syntax = "proto3";

service PedidoService {
  rpc GetPedido (GetPedidoRequest) returns (PedidoResponse);
  rpc ListarPedidos (ListarPedidosRequest) returns (stream PedidoResponse);
  rpc ProcessarPedido (ProcessarRequest) returns (ProcessarResponse);
}

message GetPedidoRequest {
  string pedido_id = 1;
}

message PedidoResponse {
  string id = 1;
  string status = 2;
  double valor_total = 3;
  int64 created_at = 4;
}
// Implementação no .NET
public class PedidoGrpcService : PedidoService.PedidoServiceBase
{
    private readonly ISender _sender;

    public override async Task<PedidoResponse> GetPedido(
        GetPedidoRequest request,
        ServerCallContext context)
    {
        var pedido = await _sender.Send(
            new GetPedidoQuery(Guid.Parse(request.PedidoId)),
            context.CancellationToken);

        return new PedidoResponse
        {
            Id = pedido.Id.ToString(),
            Status = pedido.Status.ToString(),
            ValorTotal = (double)pedido.ValorTotal.Amount
        };
    }

    public override async Task ListarPedidos(
        ListarPedidosRequest request,
        IServerStreamWriter<PedidoResponse> responseStream,
        ServerCallContext context)
    {
        await foreach (var pedido in _sender.CreateStream<PedidoResponse>(
            new StreamPedidosQuery(request.ClienteId), context.CancellationToken))
        {
            await responseStream.WriteAsync(pedido);
        }
    }
}

O que gRPC não resolve

Browser support é limitado (precisa de gRPC-Web). Debugging é difícil (binary protocol — não dá pra ler no Wireshark sem ferramentas). Load balancers tradicionais precisam de suporte a HTTP/2. Protobuf schema evolution tem regras rígidas (não delete campos, só marque deprecated).

Quando gRPC faz sentido

  • Comunicação interna entre microsserviços (não exposta ao browser)
  • Alta frequência de chamadas onde latência e throughput importam
  • Streaming de dados (logs em tempo real, eventos, pipelines de dados)
  • Múltiplas linguagens que precisam se comunicar (geração de client code a partir do .proto)
  • Substituição de comunicação síncrona em hot paths

Comparativo direto

| Critério           | REST          | GraphQL        | gRPC           |
|--------------------|---------------|----------------|----------------|
| Facilidade inicial | ⭐⭐⭐⭐⭐   | ⭐⭐⭐          | ⭐⭐⭐          |
| Performance        | ⭐⭐⭐        | ⭐⭐⭐          | ⭐⭐⭐⭐⭐      |
| Flexibilidade API  | ⭐⭐⭐        | ⭐⭐⭐⭐⭐      | ⭐⭐⭐          |
| Suporte browser    | ⭐⭐⭐⭐⭐   | ⭐⭐⭐⭐⭐      | ⭐⭐            |
| Caching HTTP       | ⭐⭐⭐⭐⭐   | ⭐              | ⭐              |
| Streaming          | ⭐⭐          | ⭐⭐⭐          | ⭐⭐⭐⭐⭐      |
| Tooling/Ecosystem  | ⭐⭐⭐⭐⭐   | ⭐⭐⭐⭐        | ⭐⭐⭐          |
| Curva aprendizado  | Baixa         | Média-Alta     | Média           |

A arquitetura híbrida que funciona na prática

A maioria dos sistemas modernos usa os três:

[ Browser / App Mobile ]
         |
    [ REST ou GraphQL ]   ← API Gateway / BFF público
         |
    [ gRPC ]              ← Comunicação interna entre microsserviços
         |
 [ Mensageria (eventos) ] ← Comunicação assíncrona entre contextos

REST ou GraphQL para APIs externas (clientes, parceiros). gRPC para comunicação interna em hot paths. Mensageria para comunicação assíncrona e eventos de domínio.

Conclusão

Use REST como default para APIs externas — é simples, universal e bem compreendido. Migre para GraphQL quando o frontend precisar de flexibilidade real de dados e o time estiver preparado para lidar com DataLoaders e complexidade. Adote gRPC nos hot paths internos onde performance é medida e justifica o custo de operação.

A pior decisão é escolher GraphQL "porque é moderno" sem entender o N+1 problem. A segunda pior é rejeitar gRPC por ser "complexo" sem medir a diferença de latência nos serviços que mais precisam de performance.

Precisa desenhar a próxima fase com menos retrabalho?

Fazemos discovery técnico para mapear riscos, arquitetura-alvo e sequência de execução antes de investir pesado.

Solicitar Discovery

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.