Hypermedia APIs: REST do jeito que Roy Fielding realmente quis dizer

1. O que é REST de verdade? Desconstruindo o mito do CRUD via HTTP

Quando desenvolvedores dizem "estou construindo uma API REST", na maioria das vezes estão apenas criando endpoints que mapeiam operações CRUD para verbos HTTP. Isso não é REST. É RPC disfarçado.

Roy Fielding, em sua tese de doutorado de 2000, definiu seis constraints arquiteturais que formam o REST verdadeiro: cliente-servidor, stateless, cache, interface uniforme, sistema em camadas e code-on-demand (opcional). O coração do sistema é a interface uniforme, que exige identificação de recursos, manipulação via representações, mensagens auto-descritivas e — o mais ignorado — hypermedia como motor do estado da aplicação.

Sem hypermedia, sua API é apenas uma coleção de endpoints fixos que o cliente precisa conhecer previamente. Isso viola o princípio fundamental de REST: o servidor deve guiar o cliente através das transições de estado possíveis.

2. HATEOAS: Hypermedia as the Engine of Application State

HATEOAS significa que o servidor entrega não apenas dados, mas também instruções sobre o que o cliente pode fazer em seguida. Em vez de o cliente saber que POST /orders cria um pedido, o servidor inclui um link ou formulário na resposta que indica essa possibilidade.

Considere uma resposta tradicional:

GET /orders/123
{
  "id": 123,
  "status": "pending",
  "total": 150.00
}

Agora, a mesma resposta com HATEOAS:

GET /orders/123
{
  "id": 123,
  "status": "pending",
  "total": 150.00,
  "_links": {
    "self": { "href": "/orders/123" },
    "pay": { "href": "/orders/123/payments", "method": "POST" },
    "cancel": { "href": "/orders/123", "method": "DELETE" },
    "items": { "href": "/orders/123/items" }
  }
}

O cliente descobre dinamicamente que pode pagar ou cancelar o pedido. Se o status mudar para "paid", o servidor remove o link "pay" e adiciona "refund". A navegação é guiada, não hardcoded.

3. Formatos de Hypermedia: HAL, Siren, JSON:API e Collection+JSON

HAL (Hypertext Application Language) é o formato mais simples. Usa _links para navegação e _embedded para recursos embutidos:

GET /books/42
{
  "_links": {
    "self": { "href": "/books/42" },
    "author": { "href": "/authors/7" },
    "reviews": { "href": "/books/42/reviews" }
  },
  "title": "RESTful Web APIs",
  "isbn": "978-1449358068",
  "_embedded": {
    "author": {
      "_links": { "self": { "href": "/authors/7" } },
      "name": "Roy Fielding"
    }
  }
}

Siren é mais rico, com actions que descrevem formulários completos:

{
  "class": [ "order", "pending" ],
  "properties": { "total": 150.00 },
  "actions": [
    {
      "name": "pay",
      "method": "POST",
      "href": "/orders/123/payments",
      "fields": [
        { "name": "payment_method", "type": "text" },
        { "name": "amount", "type": "number" }
      ]
    }
  ],
  "links": [
    { "rel": [ "self" ], "href": "/orders/123" }
  ]
}

JSON:API oferece relações, includes e paginação via links. Collection+JSON é focado em coleções com templates de consulta e edição.

Escolha HAL para simplicidade, Siren para APIs com workflows complexos, JSON:API para compatibilidade com ecossistemas existentes e Collection+JSON para APIs centradas em listas e filtros.

4. Navegabilidade e Descoberta: Clientes que aprendem com o servidor

Um cliente genérico de hypermedia não precisa de documentação de endpoints. Ele começa com uma URL raiz e segue links. Exemplo de fluxo de checkout:

GET /api/
{
  "_links": {
    "products": { "href": "/api/products" },
    "cart": { "href": "/api/cart" }
  }
}

GET /api/products
{
  "_links": { "self": { "href": "/api/products" } },
  "items": [
    {
      "name": "Livro REST",
      "_links": {
        "add-to-cart": { "href": "/api/cart/items", "method": "POST" }
      }
    }
  ]
}

O cliente descobre que pode adicionar ao carrinho. Após adicionar, a resposta do carrinho inclui:

{
  "_links": {
    "checkout": { "href": "/api/checkout", "method": "POST" }
  }
}

Sem hardcode de URLs. Se o servidor mudar os endpoints, o cliente se adapta automaticamente, desde que os links sejam seguidos.

5. Hypermedia na prática: Implementando uma API de livros com HATEOAS

Vamos modelar uma API de livros onde cada recurso expõe apenas ações permitidas pelo estado atual.

Recurso livro (disponível):

GET /books/1
{
  "id": 1,
  "title": "RESTful Web APIs",
  "status": "available",
  "authors": ["Roy Fielding"],
  "_links": {
    "self": { "href": "/books/1" },
    "borrow": { "href": "/books/1/borrow", "method": "POST" },
    "update": { "href": "/books/1", "method": "PATCH" },
    "delete": { "href": "/books/1", "method": "DELETE" }
  }
}

Após emprestar (emprestado):

GET /books/1
{
  "id": 1,
  "title": "RESTful Web APIs",
  "status": "borrowed",
  "borrowed_by": "user123",
  "due_date": "2025-04-15",
  "_links": {
    "self": { "href": "/books/1" },
    "return": { "href": "/books/1/return", "method": "POST" },
    "extend": { "href": "/books/1/extend", "method": "POST" }
  }
}

Note que os links "borrow", "update" e "delete" desapareceram. O servidor guia o cliente apenas para ações válidas. O código do servidor verifica o estado e gera os links dinamicamente:

function getBookLinks(book, user) {
  const links = { self: { href: `/books/${book.id}` } };
  if (book.status === 'available') {
    links.borrow = { href: `/books/${book.id}/borrow`, method: 'POST' };
    if (user.role === 'admin') {
      links.update = { href: `/books/${book.id}`, method: 'PATCH' };
      links.delete = { href: `/books/${book.id}`, method: 'DELETE' };
    }
  } else if (book.status === 'borrowed' && book.borrowed_by === user.id) {
    links.return = { href: `/books/${book.id}/return`, method: 'POST' };
    links.extend = { href: `/books/${book.id}/extend`, method: 'POST' };
  }
  return links;
}

6. Desafios reais: Performance, versionamento e adoção no mercado

Overhead de payload: Respostas com hypermedia podem ser 30-50% maiores. Soluções: compressão HTTP, campos opcionais via query params (?embed=links), ou formatos binários como CBOR.

Versionamento: Hypermedia permite evolução sem quebrar clientes. Em vez de /v1/books, o servidor adiciona novos links e mantém os antigos. Clientes antigos ignoram links desconhecidos. Isso é versionamento evolutivo.

Adoção no mercado: Poucas equipes adotam HATEOAS porque:
- Clientes precisam ser mais inteligentes
- Ferramentas como Swagger/OpenAPI incentivam endpoints fixos
- A maioria dos desenvolvedores nunca leu a tese de Fielding

Para superar: comece com HAL em endpoints críticos, use ferramentas de validação de hypermedia (como Hyperium) e documente os relation types (rel) em vez de URLs.

7. O futuro das Hypermedia APIs: GraphQL, JSON:API e o renascimento do REST

GraphQL não é hypermedia. Ele oferece descoberta via schema introspection, mas o cliente decide a estrutura da resposta e as transições de estado não são guiadas pelo servidor. É uma ferramenta poderosa, mas diferente.

JSON:API está evoluindo para incluir mais hypermedia. A especificação 1.1 adicionou links obrigatórios em recursos e suporte a meta com actions. É a ponte mais prática entre REST tradicional e HATEOAS.

Tendências: APIs auto-descritivas com máquinas de estado explícitas (como JSON State Machine ou State Charts) estão ganhando espaço. Ferramentas como Hydra e JSON-LD permitem descrições semânticas completas dos recursos.

O renascimento do REST verdadeiro virá quando entendermos que hypermedia não é um extra opcional — é a alma do REST. Sem ele, temos apenas RPC com verbos HTTP.

Referências