APIs - REST, GraphQL e gRPC
O eixo do trade-off
Seção intitulada “O eixo do trade-off”Todos os três resolvem “como um processo pede algo a outro”, mas otimizam variáveis diferentes: acoplamento de contrato, flexibilidade da consulta, desempenho de serialização e maturidade do ecossistema. Escolher é priorizar entre elas.
REST expõe recursos identificados por URIs e manipulados pela semântica uniforme do HTTP (verbos, status codes, cabeçalhos, cache). Sua força é a ubiquidade: qualquer cliente HTTP consome, cache de infraestrutura funciona de graça, e a curva de adoção é mínima.
Modelo de Maturidade de Richardson
Seção intitulada “Modelo de Maturidade de Richardson”- Nível 0: HTTP como túnel RPC (um endpoint, tudo POST).
- Nível 1: recursos distintos por URI.
- Nível 2: uso correto de verbos (GET/POST/PUT/DELETE) e status codes.
- Nível 3: HATEOAS — respostas carregam links para as próximas transições de estado.
A maioria das APIs “REST” fica no nível 2, o que é pragmático e suficiente. O ponto fraco do REST é o over/under-fetching: o recurso tem forma fixa, então o cliente móvel recebe campos que não usa (over) ou precisa de várias chamadas para compor uma tela (under).
GraphQL
Seção intitulada “GraphQL”GraphQL inverte o controle: o servidor publica um schema tipado e o cliente declara exatamente quais campos quer numa única query. Elimina over/under-fetching e é ideal para clientes heterogêneos (web, mobile) com necessidades de dados divergentes evoluindo rápido.
Custos:
- Cache é mais difícil (POST único, resposta variável) do que o cache de recurso do REST.
- Problema N+1: um resolver por campo de lista pode disparar uma consulta por item. Mitiga-se com DataLoader (batching + cache por request).
- Complexidade de query: clientes podem montar consultas caras; exige limites de profundidade/custo.
gRPC é RPC com contrato Protobuf compilado, trafegando binário sobre HTTP/2. Vantagens: serialização compacta e rápida, contrato fortemente tipado gerando stubs em várias linguagens, e streaming bidirecional nativo. É a escolha natural para comunicação interna de alta frequência entre serviços.
Custos: menos amigável a navegadores (exige gRPC-Web + proxy), payload binário não é human-readable, e o acoplamento ao contrato compilado é mais rígido — o que é uma vantagem para contratos internos e uma fricção para APIs públicas.
Quando usar cada um
Seção intitulada “Quando usar cada um”| Critério | REST | GraphQL | gRPC |
|---|---|---|---|
| Público-alvo | Clientes externos, APIs públicas | Clientes ricos/heterogêneos (mobile, web) | Serviço-a-serviço interno |
| Contrato | OpenAPI (opcional) | Schema GraphQL (forte) | Protobuf (forte, compilado) |
| Transporte | HTTP/1.1+ | HTTP (POST) | HTTP/2 |
| Serialização | JSON (texto) | JSON (texto) | Protobuf (binário) |
| Over/under-fetching | Sofre | Resolve | Contrato fixo por método |
| Streaming | Limitado (SSE) | Subscriptions | Nativo bidirecional |
| Cache HTTP | Excelente | Fraco | Não aplicável |
| Melhor cenário | CRUD público, cacheável | Agregação flexível para UI | Baixa latência interna |
graph LR
Mobile[Cliente Mobile] -->|GraphQL| GW[Gateway BFF]
Web[Cliente Web] -->|REST| GW
GW -->|gRPC| S1[Servico Pedido]
GW -->|gRPC| S2[Servico Estoque]
S1 -->|gRPC stream| S2
Exemplos em Spring
Seção intitulada “Exemplos em Spring”Controller REST
Seção intitulada “Controller REST”@RestController@RequestMapping("/pedidos")public class PedidoController {
private final PedidoAppService service;
public PedidoController(PedidoAppService service) { this.service = service; }
@PostMapping public ResponseEntity<PedidoResponse> criar(@RequestBody @Valid CriarPedidoRequest req) { UUID id = service.criar(req.clienteId(), req.total()); return ResponseEntity .created(URI.create("/pedidos/" + id)) .body(new PedidoResponse(id, "CRIADO")); }
@GetMapping("/{id}") public PedidoResponse buscar(@PathVariable UUID id) { return service.buscar(id); }}
record CriarPedidoRequest(@NotNull UUID clienteId, @Positive BigDecimal total) {}
record PedidoResponse(UUID id, String status, BigDecimal total) {}Schema GraphQL (Spring for GraphQL)
Seção intitulada “Schema GraphQL (Spring for GraphQL)”type Query { pedido(id: ID!): Pedido pedidosDoCliente(clienteId: ID!): [Pedido!]!}
type Pedido { id: ID! status: String! total: Float! itens: [Item!]!}
type Item { sku: String! quantidade: Int!}@Controllerpublic class PedidoGraphqlController {
private final PedidoAppService service; private final ItemLoader itemLoader;
public PedidoGraphqlController(PedidoAppService service, ItemLoader itemLoader) { this.service = service; this.itemLoader = itemLoader; }
@QueryMapping public PedidoResponse pedido(@Argument UUID id) { return service.buscar(id); }
@SchemaMapping(typeName = "Pedido", field = "itens") public CompletableFuture<List<Item>> itens(PedidoResponse pedido) { return itemLoader.load(pedido.id()); }}O @SchemaMapping do campo itens resolvido por um loader com batching é a defesa contra o problema N+1: em vez de uma query por pedido, os IDs são agrupados por request.
Contrato .proto (gRPC)
Seção intitulada “Contrato .proto (gRPC)”syntax = "proto3";
package pedido;
option java_multiple_files = true;option java_package = "com.exemplo.pedido.grpc";
service PedidoService { rpc Buscar(BuscarRequest) returns (PedidoReply); rpc StreamStatus(BuscarRequest) returns (stream StatusEvento);}
message BuscarRequest { string id = 1;}
message PedidoReply { string id = 1; string status = 2; double total = 3;}
message StatusEvento { string status = 1; int64 timestamp = 2;}Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- REST nível 0: um único endpoint POST fazendo RPC disfarçado — perde cache, verbos e status codes.
- GraphQL sem DataLoader: resolvers ingênuos geram N+1 e derrubam o banco.
- GraphQL sem limite de custo: clientes montam queries profundas e caras (DoS acidental).
- gRPC exposto direto ao navegador sem gRPC-Web/proxy, ou usado onde cache HTTP seria essencial.
- Verbos e status errados no REST:
200 OKpara erro,GETque muda estado — quebrando a semântica safe do método (GET deve ser seguro e idempotente) e, de quebra, o cache.
Relações
Seção intitulada “Relações”- Consumidores e produtores: Microservices.
- Entrada e composição de APIs: API Gateway, API Composition.
- Alternativa assíncrona: Event-Driven Architecture.
- Resiliência das chamadas: Retry, Circuit Breaker, Fallback.
- Contexto: Sistemas Distribuídos, Home.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”- O que é over/under-fetching no REST e como GraphQL o resolve?
Resposta
Recursos REST têm forma fixa: o cliente recebe campos que não usa (over-fetching) ou precisa de várias chamadas para compor uma tela (under-fetching). GraphQL deixa o cliente declarar exatamente os campos desejados numa única query tipada pelo schema, eliminando ambos.
- Por que gRPC é preferido para tráfego interno e menos indicado para clientes externos?
Resposta
Interno: Protobuf binário sobre HTTP/2 dá baixa latência, contrato tipado e streaming, ideal para chamadas frequentes serviço-a-serviço. Externo: payload não legível, suporte limitado em navegador (exige gRPC-Web + proxy) e ausência de cache HTTP tornam REST/GraphQL melhores.
- O que é o problema N+1 em GraphQL e como mitigá-lo?
Resposta
Um resolver por item de uma lista dispara uma consulta por elemento (1 da lista + N dos itens). Mitiga-se com um DataLoader que agrupa (batching) os IDs e cacheia por request, transformando N+1 consultas em poucas.
- O que separa os níveis 2 e 3 do Modelo de Maturidade de Richardson?
Resposta
O nível 2 usa recursos, verbos HTTP e status codes corretamente. O nível 3 adiciona HATEOAS: as respostas incluem links (hypermedia) para as próximas transições de estado, tornando o cliente menos acoplado às URIs.