Cover for REST API: Por que você complica? post (placeholder)Cover for REST API: Por que você complica? post

REST API: Por que você complica?

Fazendo uma API REST detalhada

6 de dez. de 2025, 02:12

Esta apresentação NÃO é sobre:

Porém se possui interesse nesses temas, recomendo muito o curso FastAPI do Zero publicado pelo Eduardo Mendes.

Esta apresentação é sobre

A web, REST API e Hipermídia

Se você atua na área de tecnologia como desenvolvedor de sofware para a web, já seja familiarizado com o termo API (Application Programming Interface - Interface de Programação de Aplicação), que pode ser toda e quaisquer aplicação que tem como objetivo interfacear de forma programável algum recurso, indo de consulta a base de dados a acesso a recursos de hardware como GPU.

Exemplos de API que podemos citar seria o uso de GPU por meio do navegador, através da API WebGL

Porém pensando em aplicações web, uma API REST(ful) - Representational State Transfer, refere-se a uma forma de controlar e restringir uma aplicação por meio de representação de estados destas em recursos. Atualmente o formato mais comum de implementação consiste em um servidor Web que, por meio do protocolo HTTP de implementação consiste em um servidor Web que por meio do protocolo HTTP (Hipertext Transfer Protocol) que entrega as informações a respeito dos estados em estrutura JSON (Javascript Object Notation).

Uma aplicação REST que contemple todos os princípios referentes à idempotência, que é a capacidade de representar todos os estados necessários de forma semântica, usando os verbos adequados (GET, POST, PUT, PATCH, DELETE...) bem como respeitando os princípios de manter o estado entre requisições, ser cacheável e possuir mensagens expressivas em caso de falha é denominada de RESTful, porém podemos usar o termo em questão de forma análoga ao próprio conceito de REST API em linhas gerais.

Exemplo prático e resumido

Uma API REST é como um restaurante, onde o estabelecimento é o servidor, o cliente por sua vez está implicito no nome.

Um exemplo de API REST aberta ao público seria a API OMDb, Open Movie Database

Componentes de uma requisição HTTP

Dado que o principal objetivo de uma requisição HTTP diz respeito à capacidade de comunicar dados entre servidor e cliente, podemos dividir esses dados em dois grupos:

Status Code

Status Code são números de 3 dígitos entregues numa requisição HTTP como forma de comunicar o resultado de forma imediata, sem precisar de serialização ou informações adicionais para tomar decisões básicas.

É importante que estes sejam usados de forma consistente, para que clientes automatizados, como frameworks, possam consumir sua API sem muitos ajustes

Podemos dividir os status code em 5 faixas.

  1. 1xx: status de informação, usados para comunicar algum tipo de informação a respeito do servidor.
    • 100: Continue, muito usado quando se quer evitar o envio de um payload muito grande, mas precisa verificar se o servidor está disponível
      • Um caso de uso curioso que eu vi isso em prática foi em fila de geração de PDF de apólices em sistemas de seguradora.
  2. 2xx: status de sucesso (é o que esperamos que aconteça no caminho feliz), representa uma operação bem-sucedida.
    • 200: Ok, simplesmente Ok
    • 201: Created, um código frequentemente negligenciado que deveria ser usado mais, principalmente quando uma operação resulta em algum tipo de dado criado, como vimos com métodos POST e PUT.
    • 202: Accepted, quando uma requisição é aceita, porém o resultado será enviado depois, isso é crucial quando se faz HTTP polling, que é o processo de verificar no servidor até a informação estar pronta. Muito comum em sistemas de fila de impressão mais antigos ou de geração de arquivos em segundo plano.
    • 204: No Content, a operação deu certo, mas não há body a ser retornado, porém os headers são úteis e, se necessário, o cache deve ser atualizado. Em casos de DELETE, é o ideal na maioria dos cenários*
  3. 3xx: status de redirecionamento, servem para informar os possíveis motivos de dados terem sido migrados
    • 301: Moved Permanently, significa que o conteúdo foi movido de URL, a nova será disponibilizada no response.
    • 302: Found, significa que o conteúdo foi encontrado e movido temporariamente, pode representar uma nova URL e um novo método.
    • 307: Temporary Redirect, semântica igual ao 302, porém significa que o método HTTP não deve ser mudado no redirect, apenas a URL.
  4. 4xx: status de erros causados pelo cliente culpa do frontend, quando algum dado enviado ou requisição feita pelo lado do cliente é inconsistente.
    • 404: Não encontrado, para rota ou conteúdo não encontrado, origem do meme 404: Not Found
    • 401: Não autenticado, significa que o usuário não forneceu a informação que determina que ele foi autenticado em algum momento. Usado como identificador de endpoints protegidos, ou seja, endpoints disponíveis apenas para usuários autenticados.
    • 403: Forbidden, significa que o usuário, independente de autenticado ou não, não possui acesso à informação devida.
    • 418: I'm a Teapot (Eu sou uma chaleira), servidor se recusa a fazer café.
  5. 5xx: status de erro causados pelo servidor culpa sua, usado principalmente para erros de mecanismos internos
    • 500: Internal Server Error, quando seu servidor tem um erro de execução, runtime ou um erro não tratável. Em ambiente de desenvolvimento e nos logs, normalmente consta a stacktrace que ocasionou a falha.
    • 502: Bad Gateway, quando um servidor de Gateway falha em encontrar ou realizar a conexão com o servidor de origem, normalmente associado a timeout entre o gateway e o servidor consumido por este.
    • 503: Service Unavailable, mostrado quando há uma falha temporária, em conjunto com uma página de erro amigável se possível para explicar de forma breve a falha. Em casos de serviços como aplicações, é recomendado devolver o Header Retry-After com informações de quando está previsto a volta do recurso. Era muito comum no período de transição para a Web 2.0 (Twitter lá por 2010 e o famigerado "Rails não escala")

Cabeçalhos HTTP (Headers)

Cabeçalhos são informações extras que podem ser enviadas, bem como recebidas por uma ou para uma API REST. Nestes, em formatos de texto (string), detalhamos informações e metadados no geral sobre o conteúdo trafegado, coisas como informar a origem da aplicação, realizar o envio de chaves de autenticação e até mesmo informar a aplicação cliente sobre a política de cache do recurso.

Exemplos de cabeçalhos comuns

image

Um caso de otimização por meio de uso de dados

HATEOAS (Hipermídia como Motor do Estado da Aplicação)

Nesta conversa, o termo Hipermidia, mais precisamente o acrônomo Hypermedia as the Engine of Application State (HATEOAS), seria uma forma de implementação de arquitetura para REST API que, por meio das hipermídias, torna a consulta e navegação pelos recursos mais dinâmica, conseguindo comunicar, por meio de metadados e padrões, formas de navegação e integração entre os recursos disponibilizados.

A principal e mais comum forma de implementação de HATEOAS em aplicações modernas é dada por meio da implementação de recursos com HAL - Hypertext Application Language, onde adicionamos a resposta do dado, possíveis links relacionados a este, como o exemplo abaixo.

{
  "_links": {
    "self": {
      "href": "http://example.com/api/book/hal-cookbook"
    }
  },
  "id": "hal-cookbook",
  "name": "HAL Cookbook"
}

Além do HAL, outros formatos para implementar HATEOS seriam JSON:API, Siren, Hydra (JSON-LD), Collection+JSON e UBER.


JSON:API

{
  "data": {
    "type": "book",
    "id": "1",
    "attributes": {
      "title": "O Senhor dos Anéis"
    },
    "links": {
      "self": "/books/1"
    },
    "relationships": {
      "author": {
        "links": {
          "related": "/books/1/author"
        }
      }
    }
  },
  "links": {
    "self": "/books"
  }
}

Siren

{
  "class": ["book"],
  "properties": {
    "id": 1,
    "title": "O Senhor dos Anéis"
  },
  "links": [
    { "rel": ["self"], "href": "/books/1" },
    { "rel": ["author"], "href": "/books/1/author" }
  ],
  "actions": [
    {
      "name": "update-book",
      "method": "PUT",
      "href": "/books/1",
      "type": "application/json",
      "fields": [
        { "name": "title", "type": "text" }
      ]
    }
  ]
}

Hydra (JSON-LD)

{
  "@context": "/contexts/Book.jsonld",
  "@id": "/books/1",
  "@type": "Book",
  "title": "O Senhor dos Anéis",
  "author": "/books/1/author",
  "hydra:operation": [
    {
      "@type": "hydra:UpdateResourceOperation",
      "hydra:method": "PUT",
      "hydra:expects": "http://schema.org/Book",
      "hydra:returns": "http://schema.org/Book",
      "hydra:target": "/books/1"
    }
  ]
}

Collection+JSON

{
  "collection": {
    "version": "1.0",
    "href": "/books",
    "items": [
      {
        "href": "/books/1",
        "data": [
          { "name": "id", "value": "1" },
          { "name": "title", "value": "O Senhor dos Anéis" }
        ],
        "links": [
          { "rel": "author", "href": "/books/1/author" }
        ]
      }
    ],
    "links": [
      { "rel": "self", "href": "/books" }
    ],
    "queries": [
      {
        "rel": "search",
        "href": "/books/search",
        "data": [
          { "name": "title", "value": "" }
        ]
      }
    ]
  }
}

UBER (Uber Hypermedia)

{
  "uber": { 
    "version": "1.0",
    "data": [
      {
        "id": "book",
        "name": "O Senhor dos Anéis",
        "rel": ["self"],
        "url": "/books/1",
        "data": [
          {
            "rel": ["author"],
            "url": "/books/1/author"
          }
        ]
      }
    ]
  }
}

HATEOS é complicado, mas não deveria ser

A principal dificuldade dos formatos comuns para HATEOS gira em torno da dificuldade de serialização:

A serialização mal otimizada pode dificultar a performance de aplicações dinâmicas, tais como feeds de redes sociais, leitura de dados em tempo real e procesos de digestão de alto volume de dados.

Outro problema menos evidente seria a obrigatoriedade de manter implementações sequenciais de problemas que seriam resolvidos de uma forma melhor por meio concorrência/paralelismo

Como resolver o problema

A solução para as dificuldades geradas pela implementação de uma arquitetura HATEOS consiste em atender os seguintes pré-requisitos

A forma mais simples de resolver isso seria por meio da implementação da RFC 8288 - Web Linking para a paginação e demais informações por meio de cabeçalhos customizados.

Ainda que muitos serviços usem o prefixo X- para headers especiais, atualmente isso é desencorajado especificamente pela RFC 6648

Desta forma, podemos recompor nosso exemplo de livros por meio do seguinte modelo para o corpo da resposta (Response Body) em uma simples lista de JSON.

[
  {
    "id": 1,
    "title": "O Senhor dos Anéis",
    "author": "R. R. Tolkein"
  }
]

Por fim, podemos ter os seguintes cabeçalhos. Nem todos são obrigatórios por questões de performance, principalmente aqueles que realizam a contagem de itens, porém o Items-Returned é uma ótima forma de saber se a lista retornou sem item algum (vazia).

HTTP/1.1 200 OK
Content-Type: application/json
Total-Count: 1000               # Total de itens no banco de dados
Total-Pages: 100                # Total de páginas (se você já calculou)
Items-Per-Page: 10              # Itens por página
Current-Page: 1                 # Página atual
Items-Returned: 1              # Total de itens retornados na página atual
Link: <https://api.exemplo.com/items?page=2>; rel="next",
      <https://api.exemplo.com/items?page=100>; rel="last",
      <https://api.exemplo.com/items?page=1>; rel="first"
      <https://api.exemplo.com/items?page=1>; rel="prev"

Considerando cenários de requisições de criação de dados, principalmente de objetos únicos, podemos usar o header Location caso este possua algum endpoint de consulta por meio de ID de referência que seja único e idempotente.

Outro ponto importante é que nem sempre poderemos configurar o domínio. Nossa API pode estar limitada por um API Gateway ou ferramenta de DNS. Nesses casos, é recomendado o uso de links relativos, isso cria um ônus para o cliente concatenar ao domínio original, mas este é disponibilizado no header Host.

Dessa forma, o exemplo que utilizamos anteriormente é capaz de evoluir para esse modelo:

HTTP/1.1 200 OK
Content-Type: application/json
Total-Count: 1000               # Total de itens no banco de dados
Total-Pages: 100                # Total de páginas (se você já calculou)
Items-Per-Page: 10              # Itens por página
Current-Page: 1                 # Página atual
Items-Returned: 1              # Total de itens retornados na página atual
Link: </items?page=2>; rel="next",
      </items?page=100>; rel="last",
      </items?page=1>; rel="first"
      </items?page=1>; rel="prev"

O uso de contadores deve ser moderado, visto que em bases grandes de dados ou com mudanças constantes, essa operação pode custar muito recurso. Nesses casos, cabe ao desenvolvedor responsável avaliar as necessidades e capacidades do projeto.

Dicas e boas práticas para uso coerente de status code

Referências