Pular para conteúdo

Backend

HTTP: o protocolo da Web

Definição: HTTP (HyperText Transfer Protocol)

Protocolo (desde 1990, junto com o HTML e o primeiro navegador) que define como um cliente (navegador, app, outro serviço) troca mensagens com um servidor para acessar/manipular um recurso — é a base de praticamente todo software que fala com a Web. Roda sobre uma conexão TCP já estabelecida (ver Redes) — o HTTP em si não se preocupa em endereçar máquinas ou garantir entrega de pacotes, isso já foi resolvido nas camadas de baixo.

O caminho de uma requisição

Antes do HTTP em si acontecer, duas coisas já resolveram "para onde" a mensagem vai:

  1. Resolução de DNS — o nome (www.pudim.com.br) é traduzido para um endereço IP.
  2. Conexão TCP — o cliente abre uma conexão TCP com o servidor, numa porta (a porta padrão do HTTP é a 80; do HTTPS, a 443 — catalogadas pela Internet Assigned Numbers Authority, IANA).

Só depois disso o HTTP em si troca sua mensagem, em texto simples, sobre a conexão já aberta:

GET /index.html HTTP/1.1
Host: www.pudim.com.br
User-Agent: curl/7.68.0
Accept: */*

Definição: Anatomia de uma requisição HTTP

A primeira linha contém o método (GET), a URI do recurso (/index.html) e a versão do protocolo (HTTP/1.1). As linhas seguintes são cabeçalhos (headers) — pares chave-valor, alguns padronizados (Host, User-Agent, Accept), mas o protocolo aceita cabeçalhos quaisquer. Uma linha em branco encerra os cabeçalhos; um corpo opcional (body) pode vir depois dela. A quebra de linha segue o padrão Windows: \r\n (Carriage Return + Line Feed).

HTTP/1.1 200 OK
Date: Sun, 24 Jan 2021 18:38:10 GMT
Server: Apache/2.4.34 (Amazon)
Content-Length: 851
Content-Type: text/html; charset=UTF-8

[corpo da resposta]

A resposta espelha a estrutura da requisição: versão do protocolo, código de status e uma frase descritiva (reason phrase — só texto, a mesma informação do código em palavras), cabeçalhos, e um corpo opcional.

Métodos HTTP

O protocolo define um conjunto fixo de operações que um cliente pode pedir sobre um recurso: GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, CONNECT. Os mais usados na prática de APIs REST são GET (ler), POST (criar), PUT (atualizar/ substituir por completo) e DELETE (remover) — ver a distinção entre eles com mais profundidade em REST, abaixo.

Códigos de status

Definição: Faixas de código de status HTTP

O primeiro dígito do código categoriza o tipo de resposta:

  • 1xx (Informacional) — o servidor recebeu a requisição, mas ainda não terminou de processá-la.
  • 2xx (Sucesso) — a requisição foi recebida, entendida e processada com sucesso.
  • 3xx (Redirecionamento) — o cliente precisa executar mais alguma ação para completar a requisição (consultar um cache, tentar em outro endereço, ...).
  • 4xx (Erro do cliente) — a requisição tem algum problema atribuível a quem a fez: falta de autorização, autenticação, ou parâmetro ausente/inválido.
  • 5xx (Erro do servidor) — a requisição em si estava correta, mas o servidor falhou ao processá-la (erro interno, alta demanda, ...).

O código exato (200, 404, 500, ...) é a informação que mais importa numa resposta — é o que o código de quem consome a API deve checar para decidir como reagir, não o texto da reason phrase (que existe só para leitura humana).

URI: as partes de um endereço

Definição: URI e suas partes

Uma URI (Uniform Resource Identifier) se divide em: Scheme://Authority/Path?Query#Fragment

  • Scheme — o protocolo usado (http, https, mongodb+srv, ...) — em HTTP, https sinaliza que a conexão roda sobre uma camada de segurança adicional (ver TLS) por cima do HTTP puro.
  • Authority — quem será acessado: usuário, senha (opcionais), servidor e porta (usuario:senha@servidor:porta) — quando a porta não é informada, assume-se a porta padrão daquele protocolo.
  • Path — qual recurso, dentro daquele servidor, está sendo endereçado.
  • Query — parâmetros adicionais, no formato chave=valor separados por &.
  • Fragment — referência a uma posição específica dentro do próprio recurso (ex.: uma âncora de página).

URIs não são exclusivas do HTTP — o mesmo formato geral aparece em connection strings de banco de dados, repositórios Git, e outros protocolos.

Métodos HTTP: idempotência

Definição: Idempotência

Propriedade (herdada da matemática) de uma operação cujo resultado é o mesmo, não importa quantas vezes ela seja aplicada. Uma requisição idempotente pode ser reenviada (por causa de uma falha de rede, um retry automático, ...) com segurança — o efeito final continua idêntico ao de enviá-la uma única vez.

Método O que faz Idempotente?
GET Recupera um recurso Sim
HEAD Como GET, mas sem corpo na resposta (só cabeçalhos) Sim
PUT Sobrescreve um recurso por completo (ou cria, se aplicável) Sim
DELETE Remove um recurso Sim
OPTIONS Retorna quais métodos são válidos para aquele recurso Sim
TRACE Ecoa a requisição recebida, para diagnóstico Sim
POST Cria um novo recurso a partir dos dados enviados Não
PATCH Aplica uma atualização parcial a um recurso Não

Definição: Por que POST não é idempotente

Um POST tipicamente cria um recurso novo a cada chamada — enviar a mesma requisição duas vezes cria dois recursos (dois pedidos, dois registros), não um só. É por isso que reenviar automaticamente um POST que falhou (sem confirmação de que ele não foi processado) é arriscado, mas reenviar um GET/PUT/DELETE que falhou é seguro.

O protocolo em si não impõe significado de negócio a cada método — quem define isso é o estilo arquitetural escolhido para a API (REST usa os métodos de forma canônica ligada a operações CRUD; SOAP e gRPC praticamente ignoram essa semântica, usando quase sempre POST). Ver REST mais abaixo.

Content negotiation: decidindo o formato

O cliente e o servidor negociam o formato da resposta através de cabeçalhos — nenhum dos dois precisa "adivinhar" o formato do outro:

Accept: application/json, text/plain, */*
Accept-Language: en,en-US;q=0.8,pt-BR;q=0.5,pt;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: iso-8859-5, unicode-1-1;q=0.8

Definição: Cabeçalhos Accept-* e quality factor

O cliente envia, em ordem de preferência, os formatos/idiomas/codificações que aceita. A prioridade de cada opção é dada pelo parâmetro q (quality factor, peso de 0 a 1) — quando omitido, o valor é 1.0 (prioridade máxima). Accept pede um tipo de mídia (application/json, text/xml, ...); Accept-Language, um idioma; Accept-Encoding, uma compressão (gzip, ...); Accept-Charset, uma codificação de caracteres. O servidor decide, dentre o que consegue oferecer, qual opção da lista melhor atende ao pedido — e informa sua escolha final na resposta, através dos cabeçalhos equivalentes sem o prefixo Accept- (Content-Type, Content-Language, Content-Encoding).

HTTP é stateless

Definição: Stateless (sem estado)

O protocolo HTTP não guarda nenhum estado da conexão entre requisições — cada requisição é completa e independente; o servidor não sabe, por conta própria, se uma requisição está relacionada a outra anterior. Isso é uma escolha de design (não uma limitação): simplifica o protocolo e facilita escalar servidores horizontalmente (qualquer servidor pode responder qualquer requisição, sem precisar "lembrar" de nada da requisição anterior). O custo é que qualquer noção de "sessão" (usuário logado, carrinho de compras, ...) precisa ser reconstruída a cada requisição, através de alguma informação enviada junto com ela — é para isso que existem cabeçalhos de autenticação, cookies e tokens.

Autenticação x autorização

Definição: Authentication x Authorization

Autenticação confirma quem é o usuário (ele é mesmo quem diz ser?). Autorização confere o que esse usuário tem permissão de fazer. O cabeçalho HTTP que carrega essa informação chama-se Authorization (não Authentication) por convenção histórica — na prática, ele quase sempre carrega uma credencial de autenticação, e é o servidor quem decide, a partir dela, quais autorizações aquele usuário tem.

Definição: HTTP Basic Authentication

Método de autenticação padrão do protocolo (RFC 2617): o cabeçalho Authorization: Basic <usuário:senha em Base64> carrega a credencial codificada (não criptografada) em Base64 — qualquer um que intercepte a requisição consegue decodificar a senha instantaneamente. Só é seguro quando combinado com HTTPS (que criptografa a conexão inteira); sobre HTTP puro, é vulnerável a um ataque man-in-the-middle (um interlocutor no meio do caminho lê e reutiliza a credencial).

Definição: Cookie

Mecanismo (RFC 6265) para o servidor guardar uma informação no cliente, que volta automaticamente em cada requisição seguinte para o mesmo domínio — usado tipicamente para guardar um identificador de sessão (Set-Cookie: session=...; expires=...). Diferente de um cabeçalho de autenticação enviado manualmente pelo cliente a cada chamada, o cookie é gerenciado pelo próprio navegador. Tem um prazo de expiração — depois dele, o cliente precisa de um cookie novo.

Definição: JWT (JSON Web Token) e Bearer

Formato de token (RFC 7519) auto-contido, dividido em três partes separadas por ., cada uma em Base64Url: header (algoritmo de assinatura usado), payload (dados do usuário — id, permissões, ...) e signature (garante que o conteúdo não foi alterado, assinado com uma chave que só o serviço de autenticação conhece). Diferente de um cookie, o JWT não depende de o servidor "lembrar" de nada — toda informação necessária já está no próprio token, verificável por qualquer serviço que tenha a chave pública correspondente. É tipicamente enviado no cabeçalho Authorization: Bearer <token> — bearer ("portador") significa que quem apresenta o token é tratado como autorizado, sem nenhuma verificação adicional de identidade.

CORS (Cross-Origin Resource Sharing)

Definição: CORS (Cross-Origin Resource Sharing)

Mecanismo que permite que recursos restritos numa página web sejam solicitados a partir de um domínio diferente daquele que serviu o recurso original. Por padrão, o navegador bloqueia uma requisição feita por uma aplicação web hospedada em https://example.com para uma API em https://api.outrodominio.com, a menos que o servidor de destino explicitamente permita essa origem via configuração de CORS — fundamental para a segurança na web, pois protege usuários de ataques como Cross-Site Request Forgery (CSRF) e outras tentativas de um site malicioso acessar dados de um usuário autenticado em outro domínio.

Definição: Como o navegador decide bloquear ou não

Ao fazer uma requisição para um domínio diferente, o navegador envia um cabeçalho Origin, informando ao servidor de destino de onde a requisição está vindo. Se o servidor aceitar, ele responde com um cabeçalho Access-Control-Allow-Origin, especificando quais domínios podem acessar aquele recurso — se o cabeçalho não estiver presente, ou não incluir o domínio da requisição, o navegador bloqueia o acesso, mesmo que a requisição já tenha chegado ao servidor.

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "https://example.com") // permite requisições de 'example.com'
public class MyController {

    @GetMapping("/api/resource")
    public String getResource() {
        return "Dados protegidos";
    }
}

Definição: CORS não é uma solução de segurança completa

Por si só, o CORS não pode ser considerado uma solução completa de segurança, porque protege apenas requisições vindas de navegadores — outros tipos de cliente HTTP, como o Postman ou o uso de curl em linha de comando, ignoram completamente essa restrição (ela é aplicada pelo navegador, não pelo servidor em si). Proteger de fato o servidor exige outras medidas de segurança, como autenticação, autorização e validação de dados.

Tratando erros

Um erro HTTP não é uma falha do protocolo — é uma resposta como outra qualquer, só que com um código indicando que algo deu errado. Um detalhe sutil (e polêmico) é a diferença entre uma busca sem resultado e um recurso que não existe:

Definição: 404 numa busca vazia? Não.

Uma busca que roda com sucesso, mas não encontra nenhum resultado, deve retornar 200 OK com uma lista vazia — a busca em si funcionou. 404 Not Found é para quando a entidade específica procurada (ex.: /usuario/123) não existe. Misturar os dois casos confunde quem consome a API: como diferenciar "busca funcionou, sem resultado" de "algo deu errado"?

Definição: Códigos de erro específicos do domínio

O HTTP não obriga a usar só os códigos padronizados — a especificação reserva a faixa 450-499 para códigos específicos de negócio, desde que a API documente o que cada um significa. Uma resposta de erro também deve ter corpo explicando o problema ({"status": 400, "message": "Parameter userId is required!"}) — retornar só o código, sem explicação, obriga quem consome a API a adivinhar a causa.

Definição: Cuidado ao expor detalhes de implementação em erros

Uma mensagem de erro nunca deve vazar informação sensível sobre a implementação interna — um stack trace completo devolvido ao cliente, por exemplo, expõe nomes de classes, caminhos de arquivo e a estrutura interna do sistema, informação útil para quem estiver tentando explorar uma vulnerabilidade.

Cache HTTP

Servidores sob alta demanda processam a mesma requisição repetidamente, mesmo quando a resposta não mudou — cache evita esse reprocessamento desnecessário, guardando (em algum ponto entre cliente e servidor) uma cópia da resposta para reutilizar em requisições futuras idênticas.

graph LR
    C[Cliente] --> Cache[Servidor de Cache]
    Cache --> H[Servidor HTTP]
    H --> DB[Base de Dados]

Definição: Cache-Control

Cabeçalho que informa por quanto tempo uma resposta pode ser reutilizada sem verificar o servidor de novo (max-age=31536000, em segundos) — mais útil para recursos estáticos (arquivos JS/CSS/imagens), que não mudam durante o ciclo de vida da aplicação. Frameworks front-end modernos costumam incluir um hash do conteúdo no próprio nome do arquivo — assim, quando o conteúdo muda, o nome muda junto, e o cache antigo nunca fica "preso" a um conteúdo desatualizado.

Para recursos dinâmicos (que podem mudar a qualquer momento), o servidor precisa de uma forma de validar se a versão em cache ainda é válida, sem reenviar o corpo inteiro da resposta quando ela não mudou:

Definição: ETag e 304 Not Modified

ETag é um cabeçalho que representa o estado atual de uma entidade (um hash do conteúdo, por exemplo) — o cliente guarda esse valor junto com a resposta em cache. Numa requisição futura, o cliente reenvia esse valor no cabeçalho If-None-Match; se o ETag do servidor ainda for o mesmo, ele responde só 304 Not Modified (sem reenviar o corpo da mensagem, economizando banda) — se for diferente, responde 200 normalmente, com o novo conteúdo e o novo ETag.

Definição: If-Match x If-None-Match

If-None-Match é para cache: "só reprocesse se o ETag for diferente do que eu tenho". If-Match é para controle de concorrência: "só execute essa modificação se o ETag não tiver mudado" (evita, por exemplo, dois clientes sobrescreverem a mudança um do outro sem perceber, cada um partindo de uma versão antiga do dado).

Alternativa mais simples ao ETag: Last-Modified (a data da última alteração do recurso) combinado com If-Modified-Since/If-Unmodified-Since — mesma ideia, só que comparando datas em vez de um hash de conteúdo.

Comunicação em tempo real: WebSocket e HTTP/2

Definição: Half-duplex x full-duplex

Num canal half-duplex, só um lado pode iniciar uma requisição por vez — é a característica do HTTP tradicional: o servidor só responde, nunca inicia uma mensagem por conta própria. Num canal full-duplex, os dois lados podem enviar mensagens a qualquer momento, sem esperar o outro perguntar primeiro.

Definição: WebSocket

Extensão do protocolo HTTP (mesma porta e cabeçalhos, mas com schema ws:// em vez de http://) que transforma uma conexão HTTP comum num canal full-duplex, através dos cabeçalhos Connection: Upgrade e Upgrade: websocket — depois do "upgrade", a conexão deixa de seguir o padrão requisição-resposta do HTTP e passa a permitir que qualquer um dos dois lados envie mensagens a qualquer momento, até o canal ser fechado. Resolve a limitação de um servidor HTTP tradicional não conseguir avisar um cliente proativamente (ex.: notificações em tempo real), sem recorrer a polling (o cliente perguntando repetidamente "mudou alguma coisa?").

Definição: HTTP/2

Revisão do protocolo focada em desempenho, não em mudar a semântica do HTTP/1.1 (os métodos, status codes e cabeçalhos continuam os mesmos). As mudanças centrais: formato binário (em vez de texto simples, mais eficiente de processar) e multiplexação de requisições numa única conexão TCP (várias requisições em paralelo, sem esperar uma terminar para começar a próxima) — incluindo Server Push, a capacidade de o servidor enviar recursos que ele já sabe que o cliente vai precisar, antes mesmo de serem pedidos.

REST, um estilo arquitetural

O termo REST (Representational State Transfer) surgiu na tese de doutorado de Roy Fielding (2000), analisando estilos arquiteturais para software baseado em rede — REST é um desses estilos, construído combinando propriedades de vários outros (cliente-servidor, sistemas em camadas, cache, ...) que já existiam isoladamente antes dele.

Definição: Arquitetura de software (vocabulário de Fielding)

Uma arquitetura é composta de elementos (componentes — os serviços em execução; conectores — os mecanismos que medeiam a comunicação entre eles; dados — a informação trafegada), configurações (as informações que os componentes precisam para atingir seus objetivos, ex.: o endereço IP de um servidor), propriedades (características intrínsecas de um componente, ex.: "não armazena estado entre conexões") e estilos (um conjunto de propriedades comuns a todo o sistema). Um sistema não precisa adotar todas as propriedades de um estilo — cabe a quem projeta decidir quais fazem sentido para o problema em questão.

As seis restrições do REST

Definição: Restrições (propriedades) do estilo REST

  1. Cliente-servidor — cada componente tem responsabilidade bem definida (separação entre quem pede e quem processa).
  2. Stateless — toda informação de estado fica no cliente; cada requisição contém tudo que é necessário para ser respondida, sem depender de uma sessão guardada no servidor (ver Stateless, acima) — permite que qualquer servidor responda qualquer cliente, essencial para escalar horizontalmente.
  3. Cacheável — as requisições podem ser cacheadas; criando uma interface uniforme onde o recurso é identificado pela URI, uma camada de cache pode viver entre cliente e servidor, evitando processamento desnecessário (ver Cache, acima).
  4. Interface uniforme — recursos são identificados pela URI e a ação desejada é identificada pelo verbo HTTP — é essa consistência que torna uma API REST previsível de usar sem ler documentação alguma vez.
  5. Sistema em camadas — a interface uniforme permite compor a arquitetura em várias camadas (cache, componentes de recursos estáticos, componentes de recursos dinâmicos) sem que o cliente precise saber disso.
  6. Código sob demanda (opcional) — o servidor pode prover, além do recurso em si, código com lógica executável pelo cliente (o que deu origem ao conceito de Single Page Application).

Como consequência direta de ser síncrono (requisição/resposta sobre o HTTP) e não suportar broadcast (uma troca sempre acontece entre exatamente dois componentes), REST não é o estilo certo para todo problema — é uma ferramenta desenhada especificamente para expor recursos gerenciáveis por uma aplicação cliente-servidor.

Construindo uma API REST

Em REST, a URI identifica o recurso; o verbo HTTP identifica a ação sobre ele — diferente do HTTP "original" (pensado para documentos), aqui a URI representa qualquer abstração que a própria API decidir. Uma URI é uma sequência de tokens separados por /, onde cada token tipicamente alterna entre tipo do recurso e identificador — permitindo compor caminhos aninhados que expressam relação entre recursos:

GET  /tiquete                          → lista todos os tiquetes
POST /tiquete                          → cria um novo tiquete
GET  /tiquete/:id                      → um tiquete específico
GET  /tiquete/autor/:autorId           → tiquetes criados por um autor
PUT  /sprint/:id/tiquete/:id           → associa um tiquete a um sprint

Definição: Não existe um mapeamento oficial verbo → ação

O protocolo não define qual verbo usar para qual operação — a interpretação mais comum (e a que a maioria dos guidelines de mercado recomenda) é GET (ler, idempotente), POST (criar, não idempotente), PUT (criar ou atualizar por completo, idempotente), PATCH (atualizar parcialmente) e DELETE (remover, idempotente) — mas nada impede uma API definir sua própria convenção. O que importa é escolher uma convenção e documentá-la, mantendo consistência em toda a API — inconsistência entre endpoints da mesma API é pior do que qualquer escolha específica de convenção.

Definição: REST x RESTful

REST é o estilo arquitetural em si (o conjunto de restrições descrito acima). RESTful descreve uma implementação concreta que segue esse estilo — uma API RESTful é uma API que segue os princípios REST, do mesmo jeito que "orientado a objetos" descreve um código que segue os princípios de OO.

Definição: \"API REST é uma API HTTP que devolve JSON\" — mito

Uma afirmação parcialmente correta e parcialmente errada. É verdade que toda API REST é uma API HTTP — mas nem toda API HTTP é REST (SOAP também roda sobre HTTP, e não é REST). E é comum uma API REST devolver JSON — mas o estilo REST não define formato de corpo nenhum (a tese de Fielding nem menciona JSON): ele se preocupa só com o design da URI e o uso dos verbos HTTP. Uma API pode seguir REST à risca e devolver XML, texto puro, ou qualquer outro formato.

O modelo de maturidade de Richardson (RMM)

Nem toda API que se diz "REST" segue os princípios do estilo na mesma medida — o Richardson Maturity Model (RMM), proposto por Leonard Richardson e popularizado por Martin Fowler, organiza essa aderência em quatro níveis progressivos, apelidados de "The Glory of REST" (a glória do REST) para o nível mais alto.

flowchart BT
    N0["Nível 0: The Swamp of POX<br/>(um único método HTTP, tudo no payload)"]
    N1["Nível 1: Resources<br/>(URIs por recurso, ainda um único verbo)"]
    N2["Nível 2: HTTP Verbs<br/>(verbos e códigos de status corretos)"]
    N3["Nível 3: Hypermedia Controls<br/>(HATEOAS)"]
    N0 --> N1 --> N2 --> N3

Definição: Nível 0 — The Swamp of POX

O nível mais baixo de maturidade: a API usa HTTP só como transporte, normalmente com um único método (POST) e uma única URI para expor todas as operações — a diferenciação entre "o que fazer" fica inteiramente no corpo (payload) da requisição. Web services SOAP tipicamente se encaixam aqui — conhecido como The Swamp of POX (Plain Old XML), um apelido irônico para essa "lama" de XML sem padronização de verbos ou URIs.

Definição: Nível 1 — Resources

Introduz o conceito de recursos: em vez de uma única URI para tudo, cada contexto de negócio ganha sua própria URI (/vendas, /estoque, /inventario) — mas ainda tipicamente usando um único verbo HTTP (POST) para todas as operações daquele recurso, variando o payload para indicar a ação.

Definição: Nível 2 — HTTP Verbs

O mínimo esperado de uma API bem modelada hoje, e onde a maioria das aplicações se encaixa: os verbos HTTP (GET, POST, PUT, PATCH, DELETE) e os códigos de status passam a carregar o significado da operação — eliminando a necessidade de variar o payload só para indicar "o que fazer". A responsabilidade sai do corpo da requisição e vai para o protocolo HTTP em si.

Verbo Quando utilizar Código de sucesso
POST Criação de recursos 201 Created
PUT Atualização de recursos 204 No Content
PATCH Atualização parcial de recursos 204 No Content
DELETE Deleção de recursos 204 No Content
GET Obtenção de recursos 200 OK

Definição: Nível 3 — Hypermedia Controls (HATEOAS)

O nível mais alto — "a glória do REST" propriamente dita. Introduz o HATEOAS (Hypermedia As The Engine Of Application State): a resposta da API passa a incluir links que indicam ao cliente quais ações/recursos ele pode acessar a seguir, a partir do estado atual — a mesma ideia de navegar por um site, onde cada página tem links claros para as próximas ações possíveis, tornando a API autoexplicativa e descobrível sem depender inteiramente de documentação externa.

{
  "id": "1",
  "marca": "<valor>",
  "links": [
    { "rel": "self", "href": "/car/1" }
  ]
}

Definição: HATEOAS é opcional, mesmo em APIs maduras

Uma API só é chamada de RESTful quando atinge o Nível 2 ou 3 de maturidade. O Nível 3 (HATEOAS), porém, deve ser adotado com ponderação: a complexidade adicional de manter e navegar por links nem sempre se justifica — a grande maioria das APIs consideradas bem modeladas no mercado hoje para no Nível 2, sem que isso seja considerado um problema.

Outros estilos: RPC, gRPC, SOAP e GraphQL

REST não é o único estilo usado sobre HTTP — vale conhecer os concorrentes mais comuns e, principalmente, quando cada um resolve melhor um problema que o REST resolveria com mais atrito.

Definição: RPC (Remote Procedure Call)

Modelo de programação (desde 1976) baseado na premissa de que cliente e servidor podem se comunicar como se estivessem no mesmo processo — quem desenvolve só precisa conhecer uma interface de código, chamando um método remoto como chamaria um método local qualquer. O protocolo de comunicação e a serialização ficam escondidos atrás de um framework, que gera esse código de acesso automaticamente.

Definição: A falácia da chamada local (RPC)

Por mais que uma chamada RPC pareça uma chamada de método local, ela nunca é — toda chamada remota atravessa uma rede de verdade, com todos os problemas que isso traz (conexão instável, servidor fora do ar, resposta que nunca chega mesmo que tenha sido processada). Tratar uma chamada RPC como garantidamente confiável, só porque a sintaxe parece local, é a origem de bugs difíceis de rastrear em sistemas distribuídos.

Definição: gRPC

Framework RPC do Google (2015) — usa Protocol Buffers (protobuf, uma linguagem/formato de serialização binária do próprio Google) para definir a interface e serializar os dados, sobre HTTP/2. Diferente do REST, o gRPC não é orientado a recursos, é orientado a serviços: em vez de desenhar uma URI, se define uma mensagem (os campos de dados) e um serviço (os métodos remotos disponíveis, cada um recebendo e devolvendo uma mensagem).

message Tiquete {
    int32 id = 1;
    string titulo = 2;
}

service TiqueteService {
    rpc getTiquetePorUsuario(EncontraTiquetePorUsuario) returns (Tiquete) {}
}

Cada campo protobuf tem um número identificador (= 1, = 2, ...) usado na serialização binária em vez do nome do campo — permite que uma mensagem evolua (adicionando ou removendo campos) sem quebrar a comunicação entre versões diferentes, desde que os números já existentes nunca sejam reaproveitados. O formato binário e o HTTP/2 tornam o gRPC mais rápido que REST/SOAP em cenários de alta performance — o custo é perder a legibilidade humana direta do JSON/XML, e a curva de aprendizado de uma ferramenta nova.

Definição: SOAP (Simple Object Access Protocol)

Estilo mais antigo que gRPC, também baseado em RPC — mensagens (requisição e resposta) trafegam como XML, sempre encapsuladas num Envelope contendo um Body. Por se limitar ao XML (sem o ganho de performance de um formato binário como o protobuf), perdeu espaço para gRPC e REST ao longo do tempo, mas continua em uso em sistemas legados e cenários corporativos que já investiram nele (ex.: um Enterprise Service Bus, ferramenta para agregar e disponibilizar vários serviços SOAP).

Definição: GraphQL

Estilo criado para resolver um problema específico: clientes diferentes (web, mobile, ...) frequentemente precisam de subconjuntos diferentes dos mesmos dados. Com REST/SOAP, atender isso exigiria endpoints diferentes (ou superdimensionados) para cada necessidade de cliente. Em GraphQL, o cliente define, a cada chamada, exatamente quais campos quer receber — tudo através de um único endpoint, evitando tanto o over-fetching (receber campos que não serão usados) quanto o under-fetching (precisar de uma segunda chamada para completar o que faltou). Não concorre diretamente com REST: pode inclusive ser implementado dentro de uma mesma API REST, compartilhando a mesma base de código.

Quando usar cada um

Nenhum estilo substitui os outros por completo — a escolha depende do problema: REST é a opção mais geral e madura para expor recursos a uma aplicação cliente-servidor comum; gRPC brilha em comunicação interna entre serviços (microsserviços conversando entre si), onde performance importa mais que legibilidade humana e os dois lados podem compartilhar os arquivos .proto gerados; SOAP ainda aparece em sistemas corporativos legados, mas raramente é escolhido para projetos novos; GraphQL vale a pena quando existem clientes com necessidades de dados muito diferentes entre si consumindo a mesma API.