Escolhendo Entre REST, gRPC e GraphQL

Já coloquei os três em produção, às vezes no mesmo sistema, e a pergunta "que estilo de API isto deveria ser?" ainda aparece em toda revisão de design. A resposta honesta é que eles são otimizados para problemas diferentes, e o erro é escolher um como religião padrão em vez de casá-lo com o formato do tráfego. Eis como eu de fato decido.

O Que Cada Um É, em Uma Frase

REST modela recursos como URLs e usa verbos HTTP contra eles. O contrato é convencional (frequentemente documentado com OpenAPI), o payload geralmente é JSON, e roda sobre HTTP puro:

GET  /orders/42
POST /orders

gRPC é RPC contract-first: você define serviços e mensagens num arquivo .proto, gera código tipado de cliente e servidor, e ele fala Protobuf sobre HTTP/2.

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
}
message GetOrderRequest { string id = 1; }

GraphQL expõe um único endpoint e um schema tipado; o cliente declara exatamente quais campos quer numa query, e o servidor os resolve.

query { order(id: "42") { total status customer { name } } }

Os Eixos Que Decidem

Quem está chamando, e de onde? Este é o maior fator. gRPC é excelente para tráfego serviço-a-serviço dentro da sua própria rede — contratos tipados, multiplexação HTTP/2, payloads binários minúsculos — mas é desajeitado a partir de um browser (precisa de uma camada de proxy para funcionar na web). REST e GraphQL são nativos de browser. Se quem chama é seu próprio front-end mobile/web, REST ou GraphQL; se é outro backend que você controla, gRPC ganha seu lugar.

Quão variados são os formatos de leitura? Endpoints REST retornam um formato fixo, o que leva a duas dores clássicas: over-fetching (o cliente mobile recebe 40 campos para renderizar 3) e under-fetching (renderizar uma tela leva cinco round trips). GraphQL existe precisamente para matar ambos — o cliente pede exatamente os campos que precisa, numa requisição. Se você tem muitos tipos de cliente cada um querendo uma fatia diferente de um grafo rico, isso é o território de casa do GraphQL. Se todo cliente quer basicamente o mesmo formato fixo, a maquinaria do GraphQL é overhead que você não precisa.

Quanto você valoriza o contrato como código? O .proto do gRPC é a única fonte da verdade, e codegen significa que uma renomeação de campo quebra o build, não a produção. O schema do GraphQL é similarmente forte e tipado. O contrato do REST é uma convenção que você mantém por fora (OpenAPI ajuda mas não é imposto por padrão), então é o mais fácil de deixar divergir.

Cache. REST vence aqui e é subestimado: respostas GET cacheiam limpo em toda camada — browser, CDN, reverse proxy — chaveadas por URL, de graça. Queries GraphQL tipicamente são POSTs com o formato no body, então cache HTTP não se aplica e você cacheia no nível de resolver/campo, o que é mais trabalho. Se cache barato de leituras na borda importa, GETs REST puros são difíceis de superar.

Streaming. gRPC tem streaming bidirecional de primeira classe sobre HTTP/2. REST faz server-sent events ou long-polling de forma desajeitada; GraphQL tem subscriptions mas são um enxerto. Streaming de verdade aponta para gRPC.

Trade-offs

Axis              REST                gRPC                    GraphQL
----------------  ------------------  ----------------------  ----------------------
Contract          convention/OpenAPI  .proto (enforced)       schema (enforced)
Payload           JSON (verbose)      Protobuf (compact)      JSON, client-shaped
Browser-native    yes                 no (needs proxy)        yes
Over/under-fetch  common              fixed per-RPC           client picks fields
HTTP caching      excellent (GET)     no                      poor (POST body)
Streaming         awkward             first-class             subscriptions (bolt-on)
Best caller       public/web clients  internal service↔service many client shapes
Tooling maturity  ubiquitous          strong (typed codegen)  good, more setup

Como Eu de Fato Escolho

  • API pública, muitos terceiros, leituras cacheáveis → REST. É a língua franca; todo mundo consegue chamar, e cache de CDN é de graça.
  • Serviço-a-serviço interno, sensível a latência, contratos tipados → gRPC. Payloads binários e multiplexação HTTP/2 compensam, e os clientes gerados removem uma classe de bugs de integração.
  • Um grafo de dados rico alimentando vários front-ends diferentes → GraphQL. Deixar cada cliente moldar seu próprio payload colapsa round trips e para a taxa de over-fetch/under-fetch.

E — o reframe que frequentemente está certo — você não precisa escolher um para o sistema inteiro. Um formato comum e saudável é gRPC entre serviços internos, com uma borda fina REST ou GraphQL para o browser. Cada fronteira recebe o estilo que serve seus callers. O que você quer evitar é escolher por moda: GraphQL porque está na moda num sistema com um cliente e formatos fixos, ou gRPC numa API pública que todo mundo depois tem dificuldade de chamar de um browser. Case o estilo com quem chama e o que precisa ler, e os três são boas ferramentas.