Estilos de API
Quatro clientes diferentes precisam da mesma informação do BoxOffice — "quais eventos estão à venda e quantos lugares ainda restam?":
- o app web, que renderiza a página de um evento combinando dados do catálogo, o mapa de assentos e a disponibilidade;
- o app mobile, numa conexão 4G congestionada na fila do estádio, onde cada kilobyte e cada ida e volta contam;
- um parceiro revendedor, que integra uma vez e depois espera que a API nunca quebre;
- o serviço
orderinterno, que pede aoinventorypara confirmar holds milhares de vezes por segundo durante a abertura das vendas.
Um único estilo de API não serve aos quatro. Esta página compara as quatro famílias que você vai encontrar na prática — REST, gRPC, GraphQL e APIs assíncronas — sobre o mesmo recurso do BoxOffice e, depois, trata do que importa mais do que o estilo: projetar bem o contrato e evoluí-lo sem quebrar ninguém.
Uma API é um contrato
Uma API (Application Programming Interface) é o contrato entre um provedor (o serviço que a implementa) e seus consumidores (o código que a chama): quais operações existem, o que elas aceitam e o que devolvem — e nada sobre como são implementadas. Um contrato estável é o que permite ao time do catalog reescrever sua camada de persistência sem avisar ninguém, e o que permite ao time mobile publicar sem esperar o backend.
Três consequências orientam tudo o que vem a seguir:
- Exponha DTOs, não entidades. Um DTO (Data Transfer Object) é uma classe cuja única função é atravessar a fronteira. Devolver a entidade JPA amarra o contrato ao schema do banco e vaza campos internos.
- O contrato é um documento, não só código. OpenAPI (REST), arquivos
.proto(gRPC), o schema GraphQL e AsyncAPI (eventos) o descrevem de forma legível por máquina, e a partir deles se geram clientes, servidores, mocks e testes. - Mudar o contrato é uma negociação. Tudo o que um consumidor consegue observar — o nome de um campo, um status code, a ordem de uma lista — faz parte dele.
Um recurso, quatro estilos
O recurso: um evento com sua disponibilidade (lugares restantes por setor). Veja como cada estilo expressa "me dê o evento evt_42 com a disponibilidade".
Recursos são identificados por URLs, operações por métodos HTTP, resultados por status codes; o contrato é um documento OpenAPI.
openapi: 3.1.0
info: { title: BoxOffice Catalog API, version: 1.4.0 }
paths:
/v1/events/{eventId}:
get:
operationId: getEvent
parameters:
- { name: eventId, in: path, required: true, schema: { type: string } }
- { name: include, in: query, schema: { type: string, enum: [availability] } }
responses:
"200":
description: The event
content:
application/json:
schema: { $ref: "#/components/schemas/Event" }
"404": { $ref: "#/components/responses/NotFound" }
components:
schemas:
Event:
type: object
required: [id, title, startsAt, venue]
properties:
id: { type: string, example: evt_42 }
title: { type: string, example: "Stadium Tour — São Paulo" }
startsAt: { type: string, format: date-time }
venue: { $ref: "#/components/schemas/VenueRef" }
availability:
type: array
items:
type: object
properties:
section: { type: string, example: "PISTA" }
seatsLeft: { type: integer, example: 1180 }
priceCents: { type: integer, example: 45000 }
Qualquer cliente HTTP consegue chamá-la — um navegador, o curl, o script de um parceiro — e as respostas de GET podem ser cacheadas por navegadores e CDNs.
As operações são chamadas de procedimento remoto declaradas num arquivo .proto; o código de cliente e servidor é gerado, e as mensagens trafegam em binário compacto sobre HTTP/2.
syntax = "proto3";
package boxoffice.inventory.v1;
import "google/protobuf/timestamp.proto";
service InventoryService {
rpc GetAvailability (GetAvailabilityRequest) returns (Availability);
// server streaming: the seat map pushes changes while the fan looks at it
rpc WatchAvailability (GetAvailabilityRequest) returns (stream AvailabilityChange);
}
message GetAvailabilityRequest {
string event_id = 1;
}
message Availability {
string event_id = 1;
repeated Section sections = 2;
google.protobuf.Timestamp as_of = 3;
}
message Section {
string name = 1;
int32 seats_left = 2;
int64 price_cents = 3;
}
message AvailabilityChange {
string section = 1;
int32 seats_left = 2;
}
Os números (= 1, = 2) são field tags: na rede trafegam apenas a tag e o valor, não o nome do campo — por isso as mensagens protobuf são compactas, e por isso uma tag nunca pode ser reutilizada.
Um único endpoint (POST /graphql) e um schema tipado; o cliente escolhe os campos que quer, possivelmente atravessando vários serviços numa só requisição.
type Query {
event(id: ID!): Event
events(city: String, first: Int = 20, after: String): EventConnection!
}
type Event {
id: ID!
title: String!
startsAt: DateTime!
venue: Venue!
availability: [SectionAvailability!]! # resolved by the inventory service
}
type SectionAvailability {
section: String!
seatsLeft: Int!
priceCents: Int!
}
query EventCard {
event(id: "evt_42") {
title
startsAt
availability { section seatsLeft }
}
}
O app mobile recebe exatamente title, startsAt e dois campos por setor — sem a descrição do local, sem imagens, sem preços que ele não exibe.
Nenhuma requisição: o provedor publica um fato quando algo muda, e os consumidores interessados assinam. O contrato é o schema da mensagem e o canal, descritos em AsyncAPI.
asyncapi: 3.0.0
info: { title: BoxOffice Inventory Events, version: 1.0.0 }
channels:
availabilityChanged:
address: inventory.availability.v1
messages:
AvailabilityChanged:
payload:
type: object
required: [eventId, section, seatsLeft, occurredAt]
properties:
eventId: { type: string, example: evt_42 }
section: { type: string, example: PISTA }
seatsLeft: { type: integer, example: 1179 }
occurredAt: { type: string, format: date-time }
operations:
publishAvailabilityChanged:
action: send
channel: { $ref: "#/channels/availabilityChanged" }
search e analytics consomem este tópico para manter suas próprias cópias atualizadas; ninguém chama o inventory para perguntar.
REST na prática
REST (Representational State Transfer) foi definido por Roy Fielding como um estilo arquitetural sobre a semântica do HTTP12. Na prática, "API REST" significa o nível 2 de maturidade de Richardson: recursos com URLs próprias, métodos HTTP e status codes corretos3.
Recursos e métodos do BoxOffice
| Método + caminho | Significado | Seguro | Idempotente | Sucesso |
|---|---|---|---|---|
GET /v1/events?city=sao-paulo&cursor=… | listar eventos | sim | sim | 200 |
GET /v1/events/{id} | um evento | sim | sim | 200 |
POST /v1/events/{id}/holds | reservar lugares por 10 min | não | não → precisa de uma Idempotency-Key | 201 + Location |
DELETE /v1/holds/{holdId} | liberar um hold | não | sim | 204 |
POST /v1/orders | fechar a compra de um hold | não | não → precisa de uma Idempotency-Key | 202 (pagamento pendente) ou 201 |
GET /v1/orders/{id} | status do pedido | sim | sim | 200 |
PATCH /v1/fans/me | atualizar campos do perfil | não | não garantido | 200 |
Seguro significa que a requisição não altera o estado do servidor (caches e crawlers podem chamá-la à vontade). Idempotente significa que executá-la duas vezes tem o mesmo efeito que executá-la uma vez, então é seguro repeti-la. POST não é nenhum dos dois — e é por isso que todo POST que cria estado envolvendo dinheiro no BoxOffice exige uma chave de idempotência (integrações).
Status codes que carregam significado
| Código | Significado no BoxOffice | O cliente deve… |
|---|---|---|
200 OK / 201 Created | feito / criado (com Location) | seguir em frente |
202 Accepted | pedido recebido, pagamento ainda em processamento | consultar GET /orders/{id} ou esperar um push |
400 Bad Request / 422 Unprocessable Content | malformada / semanticamente inválida (7 lugares, o máximo é 6) | corrigir a requisição — nunca repetir |
401 / 403 | não autenticado / não autorizado | fazer login / desistir |
404 Not Found | evento inexistente | desistir |
409 Conflict | lugar já reservado por outra pessoa | escolher outros lugares |
429 Too Many Requests | rate limit atingido (bots na abertura das vendas) | esperar o Retry-After e então repetir |
503 Service Unavailable | sobrecarregado ou dependência fora do ar | repetir com backoff + jitter |
Erros merecem um corpo uniforme. A RFC 9457, Problem Details for HTTP APIs, define um (application/problem+json) que o Spring Boot (3 em diante) suporta nativamente via ProblemDetail4:
{
"type": "/problems/seat-unavailable",
"title": "Seat unavailable",
"status": 409,
"detail": "Seat C-14 is held by another fan.",
"instance": "/v1/events/evt_42/holds",
"unavailableSeats": ["C-14"]
}
Paginação: offset vs cursor
Offset (?page=3&size=20) | Cursor / keyset (?after=eyJpZCI6...&limit=20) | |
|---|---|---|
| Consulta | ORDER BY starts_at LIMIT 20 OFFSET 40 | WHERE (starts_at, id) > (:t, :id) ORDER BY starts_at, id LIMIT 20 |
| Páginas profundas | lento: o banco ainda percorre as linhas puladas | rápido: um index seek |
| Inserções durante a paginação | as linhas se deslocam → duplicatas ou buracos | estável |
| Pular para a página 37 | sim | não (só próxima/anterior) |
| Use para | tabelas administrativas pequenas | feeds, catálogos, qualquer coisa grande ou viva |
O cursor é uma string opaca (muitas vezes o base64 da chave de ordenação da última linha) que o cliente devolve sem alterar.
Cache e operações longas
- Requisições condicionais: o
catalogdevolveETag: "v17"eCache-Control: public, max-age=30; um cliente que repete a requisição comIf-None-Match: "v17"recebe304 Not Modifiedsem corpo. A CDN (edge) faz o mesmo em escala — mais um motivo para manter as leituras comoGETs cacheáveis. - Operações longas: nunca segure uma conexão por 30 s. Responda
202 Acceptedcom um recurso de status emLocation: /v1/orders/ord_9, e deixe o cliente consultá-lo ou receber um push. O checkout usa exatamente isso enquanto o provedor de pagamentos decide.
O contrato no código
Com o springdoc-openapi, o documento OpenAPI é gerado a partir do controller anotado (code-first); alternativamente, o YAML é escrito primeiro e a interface do controller é gerada a partir dele (contract-first, por exemplo com o openapi-generator). Contract-first combina com APIs consumidas por parceiros; code-first é mais rápido para as internas.
@RestController
@RequestMapping("/v1/events")
@Tag(name = "Events")
class EventController {
private final EventQueries queries;
EventController(EventQueries queries) { this.queries = queries; }
@GetMapping("/{eventId}")
@Operation(summary = "Get one event, optionally with seat availability")
ResponseEntity<EventDto> get(@PathVariable String eventId,
@RequestParam(required = false) Include include) {
EventDto event = queries.find(eventId, include == Include.AVAILABILITY)
.orElseThrow(() -> new ErrorResponseException(HttpStatus.NOT_FOUND));
return ResponseEntity.ok()
.eTag(event.version())
.cacheControl(CacheControl.maxAge(Duration.ofSeconds(30)).cachePublic())
.body(event);
}
}
O hands-on 1 constrói uma versão mais simples deste controller (quadro abaixo).
No código do BoxOffice: o contrato do catalog (lab 1)
O Hands-on 1 — API é code-first: o springdoc lê o controller e publica o documento OpenAPI sob o prefixo do próprio serviço, /catalog/openapi (Swagger UI em /swagger-ui.html), para que o documento passe pela borda e pelo gateway junto com a API que descreve. O POST responde 201 com um Location, e todo erro é application/problem+json. Compare com o ideal acima: ainda não há /v1 (acrescente antes que um consumidor externo dependa desses caminhos), a paginação é por offset (page, size ≤ 50) sobre 200.000 eventos semeados, barata nas primeiras páginas e lenta nas profundas, e não há ETag: o lab barateia as leituras com o Redis (lab 5).
@RestController
@RequestMapping("/catalog/events")
public class EventController {
private final CatalogService service;
public EventController(CatalogService service) {
this.service = service;
}
@GetMapping
public PageOut<EventOut> list(
@RequestParam(required = false) String city,
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(50) int size) {
return service.upcomingEvents(city, page, size);
}
@GetMapping("/{id}")
public EventOut get(@PathVariable Long id) {
return service.findEvent(id);
}
@PostMapping
public ResponseEntity<EventOut> create(@Valid @RequestBody EventIn in) {
var out = service.createEvent(in);
return ResponseEntity.created(URI.create("/catalog/events/" + out.id())).body(out);
}
gRPC na prática
gRPC é um framework de RPC criado pelo Google, construído sobre HTTP/2 e Protocol Buffers5. O que você ganha:
- Clientes gerados e tipados em uma dúzia de linguagens a partir de um único
.proto— chamarinventory.getAvailability(req)parece uma chamada de método local. - Mensagens binárias compactas e parsing barato: payloads protobuf costumam ser várias vezes menores que o JSON equivalente, o que importa para tráfego interno intenso.
- Multiplexação: muitas chamadas concorrentes sobre uma única conexão HTTP/2.
- Streaming: unário, server streaming (o
WatchAvailabilityacima), client streaming e bidirecional. - Deadlines que se propagam: quem chama diz "preciso de uma resposta em até 300 ms", e cada salto sabe quanto tempo resta.
E o que custa:
- Navegadores não conseguem chamá-lo diretamente: você precisa de gRPC-Web ou Connect através de um proxy, ou de uma fachada REST/GraphQL na borda.
- O balanceamento de carga é sutil: uma única conexão HTTP/2 de longa duração carrega todas as chamadas, então um load balancer L4 prende todo o tráfego de um cliente a um único servidor. Use um proxy L7 (Envoy, um service mesh) ou balanceamento no cliente (edge).
- Mais difícil de depurar à mão: binário na rede; use
grpcurle server reflection.
Uso típico: chamadas internas, de alto volume, entre serviços — order → inventory durante a abertura das vendas — em que os dois lados são seus.
GraphQL na prática
GraphQL é uma linguagem de consulta e um runtime para APIs6. O cliente envia uma query descrevendo o formato dos dados que quer; os resolvers do servidor buscam cada campo, possivelmente em serviços diferentes.
- Sem over-fetching nem under-fetching: uma única ida e volta devolve exatamente o que uma tela precisa — atraente para mobile.
- Um schema tipado com introspecção: ótimo ferramental e autodocumentação.
- Evolução sem versões: adicione campos livremente, marque os antigos com
@deprecatede remova-os quando o uso chegar a zero.
Os problemas para os quais é preciso se planejar:
- O problema N+1: listar 20 eventos, cada um com
availability, dispara ingenuamente 1 + 20 chamadas aoinventory. Agrupe-as com um DataLoader (junte os 20 ids e faça uma só chamada). - Cache é mais difícil: as queries costumam ir como
POSTpara uma única URL, então caches HTTP e CDNs não ajudam por padrão. Persisted queries (o cliente envia o hash de uma query pré-registrada, possivelmente comGET) recuperam a cacheabilidade e bloqueiam queries arbitrárias. - Queries abusivas: uma query profundamente aninhada pode ser muito cara. Limite a profundidade e calcule um orçamento de custo de query por cliente.
- Autorização por campo, não por endpoint.
Uso típico: um BFF (Backend for Frontend) — uma camada GraphQL por front-end, que compõe catalog, inventory e search para as telas. Organizações grandes usam federation para que cada time seja dono de uma parte de um único grafo.
APIs assíncronas
Nem toda interação é uma pergunta que precisa de resposta imediata. Os estilos assíncronos eliminam a espera de quem chama:
| Estilo | Direção | Uso no BoxOffice | Visto em |
|---|---|---|---|
| Eventos num broker (Kafka, Pub/Sub) | o provedor publica, muitos consumidores | OrderConfirmed → notification, analytics, search | mensageria |
| Comandos numa fila | um produtor, um entre N workers | "gerar o PDF do ingresso do pedido 9" | trabalho assíncrono |
| Webhooks | o provedor chama a sua URL quando algo acontece | o provedor de pagamentos avisa o payment que uma cobrança foi aprovada | integrações |
| WebSocket / SSE | o servidor envia para um cliente conectado | mapa de assentos ao vivo, "sua posição na fila: 18.204" | edge — protocolos |
O contrato continua importando: schemas de eventos precisam da mesma disciplina que payloads REST — documentados (AsyncAPI), versionados e evoluídos de forma compatível (schema registry, mensageria).
Escolhendo um estilo
| REST | gRPC | GraphQL | Assíncrono (eventos/filas) | |
|---|---|---|---|---|
| Transporte | HTTP/1.1, 2, 3 | HTTP/2 | HTTP | protocolo do broker (Kafka, AMQP), HTTP para webhooks |
| Payload | JSON (texto) | protobuf (binário) | JSON | JSON, Avro, protobuf |
| Contrato | OpenAPI (opcional!) | .proto (obrigatório) | schema (obrigatório) | AsyncAPI, schema registry |
| Nativo no navegador | sim | não (gRPC-Web/Connect) | sim | WebSocket/SSE para push |
| Cache HTTP | excelente (GET, ETag, CDN) | nenhum | difícil (persisted queries) | não se aplica |
| Streaming | SSE, chunked | embutido (4 tipos) | subscriptions | inerente |
| Acoplamento | requisição/resposta, temporal | requisição/resposta, temporal, geração de código | requisição/resposta, temporal | nenhum no tempo: consumidores podem estar fora do ar |
| Melhor para | APIs públicas e de parceiros, CRUD, leituras cacheáveis | chamadas internas de alto QPS, serviços poliglotas, streaming | front-ends que compõem muitos serviços (BFF) | efeitos colaterais, fan-out, trabalho que pode esperar |
flowchart TD
q1{"Quem chama precisa<br/>do resultado para continuar?"} -->|não| async["Assíncrono: evento ou fila<br/>(ou um webhook para um parceiro)"]
q1 -->|sim| q2{"Quem é o consumidor?"}
q2 -->|"navegador, parceiro,<br/>público"| q3{"Uma tela precisa de dados<br/>de muitos serviços?"}
q3 -->|sim| gql["BFF GraphQL<br/>(REST por baixo)"]
q3 -->|não| rest["REST + OpenAPI"]
q2 -->|"serviço interno,<br/>alto volume ou streaming"| grpc["gRPC"]
q2 -->|"serviço interno,<br/>volume moderado"| rest Sistemas reais misturam todos eles. No BoxOffice: REST na borda pública e para parceiros, um BFF GraphQL para os apps web e mobile, gRPC entre order e inventory, eventos para tudo o que acontece porque um pedido foi confirmado, e webhooks chegando do provedor de pagamentos.
Evoluindo um contrato
APIs mudam. A primeira pergunta diante de qualquer mudança é se ela é incompatível (breaking) — se um consumidor existente, sem alteração, deixaria de funcionar:
| Compatível (sem nova versão) | Incompatível (exige nova versão ou migração) |
|---|---|
| adicionar um endpoint, RPC ou campo de query | remover ou renomear um endpoint, RPC ou campo |
| adicionar um campo opcional na requisição | adicionar um campo obrigatório na requisição |
| adicionar um campo na resposta | remover um campo da resposta, ou mudar seu tipo ou significado (price em centavos em vez de reais) |
| aceitar um novo valor de enum na entrada | devolver um novo valor de enum que os consumidores não sabem tratar |
| adicionar um novo tipo de evento | mudar o significado de um evento existente |
Mudanças compatíveis dependem de consumidores leitores tolerantes (tolerant readers): eles ignoram campos que não conhecem (o Jackson faz isso por padrão no Spring Boot — mantenha assim). Cada estilo tem suas próprias regras:
- REST/JSON: leitores tolerantes + campos opcionais; mudanças incompatíveis vão para uma nova versão major.
- Protobuf: nunca altere nem reutilize uma field tag; ao apagar um campo, marque sua tag como
reservedpara ninguém reutilizá-la; adicionar campos é sempre seguro. - GraphQL: sem versões — adicione campos, deprecie com
@deprecated(reason: …)e remova quando a telemetria de uso mostrar que ninguém mais os pede. - Eventos: o schema registry verifica a compatibilidade a cada nova versão; eventos antigos ficam no log para sempre, então os consumidores precisam ler todas as versões que já foram escritas.
Quando uma mudança incompatível é inevitável, versione e migre:
| Estratégia | Exemplo | Observações |
|---|---|---|
| Caminho da URI | /v1/orders → /v2/orders | a mais visível; mais fácil de rotear e testar — a escolha comum |
| Header / media type | Accept: application/vnd.boxoffice.v2+json | URLs mais limpas; mais difícil de testar à mão |
| Pacote (gRPC) | boxoffice.inventory.v1 → .v2 | as duas versões do serviço podem rodar lado a lado |
Depois, expanda e contraia (parallel change): publique a v2 ao lado da v1, migre os consumidores, acompanhe o tráfego da v1 cair a zero, anuncie a data com o header Sunset (RFC 8594)7 e só então remova a v1.
Testes de contrato orientados ao consumidor
Como saber que uma mudança é compatível antes do deploy? Cada consumidor publica as interações de que depende ("o order chama GET /v1/holds/{id} e lê status e expiresAt"); o CI do provedor as executa contra a nova versão e quebra o build se alguma falhar. Ferramentas: Pact, Spring Cloud Contract. Isso transforma "será que quebramos alguém?" de um incidente em produção num build vermelho.
Na entrevista
A etapa de API do framework de entrevista leva de 3 a 5 minutos: liste os poucos endpoints que importam, com método, caminho, parâmetros principais e resposta — não o CRUD inteiro. Para o BoxOffice:
GET /v1/events/{id}/availability→ setores e lugares restantes (cacheável por alguns segundos)POST /v1/events/{id}/holds{seats}+Idempotency-Key→201 {holdId, expiresAt}ou409POST /v1/orders{holdId, paymentToken}+Idempotency-Key→202 {orderId, status: PENDING}GET /v1/orders/{id}→ status
Mencione paginação (cursor), idempotência nas escritas, rate limits, e diga por que escolheu o estilo ("REST na borda pela cacheabilidade e pelos parceiros; gRPC internamente entre order e inventory"). Entrevistadores percebem quando você sabe quais chamadas são repetidas e quais não podem duplicar.
Boas práticas
- Publique o contrato a partir do build e faça o CI falhar em mudanças incompatíveis — gere o documento OpenAPI em todo build e compare com a última versão publicada (oasdiff, openapi-diff): um campo removido quebra o pipeline, não um consumidor. Na BoxOffice: o springdoc publica o documento em
/catalog/openapi(lab 1, quadro acima). - Limite toda coleção — um tamanho de página padrão e um máximo, recusado com
400acima do máximo. Na BoxOffice:size≤ 50 noGET /catalog/events(lab 1). - Devolva um único formato de erro em todo lugar — Problem Details da RFC 9457, com uma URI
typeestável na qual os clientes possam decidir, nunca uma mensagem de texto livre que eles precisem interpretar. Na BoxOffice:spring.mvc.problemdetails.enabled: trueem todo serviço. - Prefira ids opacos e não sequenciais para recursos privados — um id sequencial revela volume e convida à enumeração dos objetos de outros usuários. Na BoxOffice: as reservas usam UUIDs (lab 6); os eventos públicos mantêm os ids do banco.
- Descontinue com uma data e uma métrica — anuncie com o cabeçalho
Sunset(RFC 8594), conte as chamadas por versão e remova a versão antiga quando o tráfego dela chegar a zero, não quando o calendário mandar.
Armadilhas comuns
- Vazar a implementação: devolver entidades, ids do banco ou nomes internos de enums que você nunca mais vai conseguir mudar.
- APIs tagarelas (chatty): uma tela que precisa de 12 chamadas sequenciais fica lenta no mobile; agregue num BFF ou projete recursos mais grossos.
- Verbos nas URLs (
/createOrder,/getEvents): RPC disfarçado de REST; use métodos sobre recursos. 200 OKcom{"error": ...}: quebra retries, monitoramento e caches — use o status code.- Escritas não idempotentes sem chaves: um timeout mais um retry é igual a uma cobrança em dobro.
- Mudanças incompatíveis silenciosas: renomear um campo passa nos testes do provedor e quebra todos os consumidores — use testes de contrato.
- gRPC atrás de um load balancer L4: todas as chamadas grudam numa única réplica.
Como se conecta
flowchart LR
CON["API = contrato<br/>OpenAPI, proto, schema"] --> REST["REST<br/>público, cacheável"]
CON --> GRPC["gRPC<br/>interno, alto volume"]
CON --> GQL["BFF GraphQL<br/>uma tela, muitos serviços"]
CON --> ASYNC["assíncrono: eventos, filas,<br/>webhooks → Mensageria"]
REST --> HTTP["status codes, paginação por cursor,<br/>Cache-Control → CDN"]
REST --> IDEM["idempotency keys<br/>→ Integrações"]
GRPC --> H2["HTTP/2, streaming, deadlines<br/>→ Protocolos da borda"]
CON --> EVO["evolução compatível,<br/>testes de contrato"]
EVO --> VER["mudança incompatível: nova versão,<br/>expandir e contrair"]
ASYNC --> SCH["schemas de eventos também evoluem<br/>→ Semântica de mensageria"] Componentes da paleta: app (todo serviço publica um contrato) · client · mobile (consumidores da API REST e do BFF GraphQL) · cdn (cacheia os GETs cacheáveis) · queue (comandos) · kafka (eventos como OrderConfirmed) · payment (chama a BoxOffice de volta por webhook). Cenários: event-fanout, payment-system, chat (push do servidor). Continua em: Borda — protocolos, Borda — API gateway, Mensageria — evolução de schema, Integrações — idempotency keys, Hands-on 1.
Pontos-chave
- Uma API é um contrato; descreva-o de forma legível por máquina (OpenAPI,
.proto, schema GraphQL, AsyncAPI) e exponha DTOs, não entidades. - REST para APIs públicas e cacheáveis, gRPC para chamadas internas de alto volume, GraphQL para front-ends que compõem muitos serviços, assíncrono quando quem chama não precisa esperar — sistemas reais misturam todos.
- Projete REST com métodos e status codes corretos, erros em Problem Details, paginação por cursor, headers de cache,
202para operações longas e chaves de idempotência para escritas não seguras. - Evolua de forma compatível (leitores tolerantes, campos opcionais, nunca reutilizar tags protobuf,
@deprecated); versione só para mudanças incompatíveis, depois expanda e contraia; pegue as quebras com testes de contrato.
-
RFC 9110 — HTTP Semantics. IETF, 2022. ↩
-
FIELDING, R. T. Architectural Styles and the Design of Network-based Software Architectures. Tese de doutorado, UC Irvine, 2000. ↩
-
FOWLER, M. Richardson Maturity Model. martinfowler.com, 2010. ↩
-
RFC 9457 — Problem Details for HTTP APIs. IETF, 2023. ↩
-
gRPC — Core concepts, architecture and lifecycle; Protocol Buffers — Language guide (proto 3). ↩
-
GraphQL Specification; OpenAPI Specification; AsyncAPI Specification. ↩
-
RFC 8594 — The Sunset HTTP Header Field. IETF, 2019. ↩