HATEOAS – APIs RESTful e o modelo de maturidade de Richardson

APIS RESTful com Delphi e DataSnap - DevSpace
APIS RESTful com Delphi e DataSnap

Última atualização em 9 de outubro de 2024 por Willian Tuttoilmondo

HATEOAS – O RESTFful em sua essência

Quando, em sua tese de doutorado, Roy Fielding descreveu o padrão REST – do inglês, REpresentational State Transfer -, buscando solucionar os problemas de complexidade e escalabilidade dos serviços SOAP – do inglês, Simple Object Access Protocol -, mal imaginava ele que, mais de 20 anos depois, muitas pessoas ainda sequer implementam suas definições mais básicas. Muitas são as APIs que, de uma forma ou outra, deixam de lado as vantagens da implementação do modelo em sua essência, fazendo uso de implementações que, quando muito, usam os verbos HTTP de maneira apropriada. Mas, antes de falarmos sobre o HATEOAS e o modelo de maturidade de Richardson, vamos relembrar um pouco como funciona uma API RESTfull.

Descrito em 2000, o modelo REST tinha como base a comunicação HTTP/1.1, a qual permite a utilização de “verbos” para determinar qual é a ação a ser tomada pelo servidor ao receber uma requisição. Assim, uma requisição de consulta utilizaria o verbo GET para obter dados no servidor e exibí-los na camada cliente. Já a inclusão dos dados se daria utilizando a mesma rota de consulta, contudo, usando o verbo POST para indicar a ação de inclusão. Os demais verbos, como PUTPATCHDELETEHEADOPTIONS discutiremos mais adiante, sendo estes sete os mais utilizados dentre todos os verbos definidos pelo protocolo HTTP.

A comunicação em uma API RESTful é feita, essencialmente, com o tráfego de objetos JSON – do inglês, JavaScript Object Notation – sobre o protocolo HTTP. Isso simplifica muito o modelo de comunicação em relação ao seu antecessor em dois pontos que são cruciais para o sucesso da comunicação: além de mais simples e inteligível que o conteúdo XML trafegado pelo SOAP, ele é muito mais “enxuto”, sem a necessidade de abrir e fechar tags de maneira redundante, o que diminui muito o payload de comunicação, e o processamento de um objeto JSON é infinitamente mais simples, fazendo com que, tanto servidor quanto cliente, respondam mais prontamente, ampliando o desempenho em ambas as pontas.

O HATEOAS e o modelo de maturidade de Richardson

HATEOAS, que assim como HTTP, REST e SOAP também é um acrônimo, o qual vem de Hypermedia As The Engine Of Application State, é um modelo proposto por Leonard Richardson e que descreve o comportamento ideal de uma API RESTful, no qual um cliente pode, sem a necessidade de conhecer todas as funcionalidades desta API, poder utilizar todos os seus recursos com as informações fornecidas por ela em uma simples consulta. Mas, antes de chegar neste nível de maturidade, as APIs podem ser classificadas de outras formas, em outros níveis. A imagem abaixo, mesmo em inglês, nos dá uma boa ideia de como isso funciona.

HATEOAS - OS níveis de maturidade de Richardson
HATEOAS – Os níveis de maturidade de Richardson

Aqui vemos 4 níveis de maturidade, indo do 0 até o 3 – bem coisa de programador mesmo. Como é possível ver, também, o HATEOAS só é alcançado no nível máximo de maturidade onde, além das informações sobre um recurso disponível na API, você também tem acesso a outras informações que te permitem navegar por todos recursos oferecidos por ela.

Se pensarmos em universo onde os micro serviços são grande parte da implementação de negócios no mundo SaaS – do inglês, Software as a Service -, ter acesso a tudo que um micro serviço possa te oferecer é uma vantagem imensa quando pensamos, em termos de arquitetura, numa integração ou aplicação cliente. Mas, antes, vamos falar mal de (quase) tudo que não é HATEOAS antes de falarmos dele.

Nível 0 – O “pântano da varíola”

O nível mais baixo de maturidade, o nível 0, chamado por Richardson de The Swamp of POX, é o que de pior pode existir na implementação de uma API. Neste nível, não temos a separação de recursos de maneira distinta, onde quem determina as ações é o endpoint indicado na rota a ser consumida. É comum, em casos assim, que apenas um ou dois verbos sejam utilizados, geralmente GET e POST. Isso é muito comum em APIs que implementem o modelo RPC – do inglês, Remote Procedure Calls – e que não estabelecem nenhuma relação com os recursos da API. APIs implementadas desta forma sequer podem ser consideradas APIs RESTful e sem sua documentação, seu consumo é praticamente inviável.

Swamp of POX

VERBOAÇÃOENDPOINT
GETConsulta/api/consultaProduto?id=25
POSTInclusão/api/cadastraProduto
GETExclusão/api/excluiProduto?id=25
POSTAlteração/api/atualizaProduto?id=25

É fácil perceber que a implementação de uma API como esta apenas faz a chamada de procedimentos remotos, replicando seus nomes e estabelecendo conexão direta com eles, passando muito longe do HATEOAS e, até mesmo, do REST. Em um segundo nível, isso melhoraria de alguma forma?

Nível 1 – Pelo menos temos os recursos

O nível 1 de maturidade de uma API já nos permite um alcance mínimo das definições do padrão REST. Nele, podemos fazer o uso mais adequado dos verbos HTTP, assim como podemos definir rotas idênticas para ações diferentes. Os recursos são representados por substantivos no plural e ações CRUD – do inglês, Create, Read, Update and Delete – podem ser executadas diretamente por meio do próprio recurso.

Resources

VERBOAÇÃOENDPOINT
GETConsulta/api/produtos/25
POSTInclusão/api/produtos
PUTAlteração/api/produtos/25
DELETEExclusão/api/produtos/25

A compreensão da API como um todo é mais simples, mas ainda precisaremos de sua documentação para conhecê-la melhor, uma vez que suas respostas são simples e a gama de aspectos cobertos pela API é bem restrito. Assim uma resposta para a consulta de um produto específico retornaria:

O retorno nos traz as informações que precisamos sobre o produto, mas é só isso. Nós até sabemos que, usando os verbos POST, PUT e DELETE, por exemplo, podemos incluir um novo registro, modificar o atual ou excluí-lo do cadastro, mas nada além disso. E, além disso tudo, nem sempre poderemos efetuar operações mais complexas por meio apenas do recurso. É muito provável que, para adicionarmos o produto ao carrinho de compras, por exemplo, um endpoint como /api/adicionarProduto?carrinho=12&produto=25 seja chamado para efetuar a operação, mais uma vez passando longe do que o REST ou o HATEOAS estabelecem. Se continuarmos seguindo, a coisa melhora?

Nível 2 – Cada verbo no seu devido lugar

Quando chegamos no nível 2 de maturidade, as coisas começam a melhorar de verdade. Assim como no nível 1, o nível 2 faz uso extensivo dos verbos HTTP para garantir que cada operação de um recurso seja executada da forma como se espera. Este nível implementa, em si, o que Fielding gostaria que todos fizessem quando utilizassem o modelo que ele elaborou há mais de duas décadas. É fácil perceber como isso funciona. Basta olharmos a tabela a seguir:

HTTP Verbs

VERBOAÇÃOENDPOINT
GETConsulta o recurso/api/vendor/v1/produtos/25
GETExecuta uma ação do recurso/api/vendor/v1/produtos/25/adicionaAoCarrinho/12
POSTInclusão/api/vendor/v1/produtos
PATCHAlteração parcial/api/vendor/v1/produtos/25
PUTAlteração completa/api/vendor/v1/produtos/25
DELETEExclusão/api/vendor/v1/produtos/25
HEADConsulta a existência do recurso/api/vendor/v1/produtos/25
OPTIONSRequisita as informações a respeito do servidor e seus cabeçalhos de resposta/api/vendor/v1/produtos/25

Aqui vemos mais verbos sendo utilizados além dos 4 usados para operações CRUD convencionais. Só para a atualização de um recurso, temos PUT, que substitui completamente um recurso no servidor, e PATCH, que faz isso de forma parcial.

Vemos também o verbo HEAD, que faz no servidor uma consulta pelo recurso, trazendo sua resposta específica – 200 para sucesso, 400 para uma requisição mal formatada, 401 para uma requisição sem autorização e 404 caso o recurso não seja encontrado, só para trazer o mínimo -, mas sem o payload da própria resposta, o que economiza tráfego quando precisamos apenas verificar a existência – ou não – de um recurso no servidor. Um exemplo disso é: quando vamos adicionar o produto 25 ao carrinho de compras 12, podemos fazer as requisições HEAD para o produto 25 e para o carrinho 12 e verificar se ambos existem antes de realmente chamar o endpoint que executa esta operação.

Também temos o verbo OPTIONS, muito utilizado por aplicações web construídas com JavaScript, para requisitar ao servidor informações que podem interferir no comportamento do cliente: os famosos cabeçalhos CORS – do inglês, Cross-Origin Resource Sharing.

Um item interessante do nível 2 é a implementação de itens de boas práticas para a publicação de uma API. Se observarmos os endpoints notaremos algumas informações que não tínhamos antes. Se decuparmos o endpoint como um todo, notaremos que ele possui uma estrutura bem definida, sendo ela:

/api/{service}/{version}/{resource}[/{id}[/{action}[/{parameter}]*]*]

Analisando esta estrutura, vemos que ela pode nos trazer vantagens na organização dos recursos e operações a serem disponibilizadas pela API. Assim, cada termo pode ser especificado da seguinte maneira:

  • Service: o nome do serviço a ser acessado, no caso, vendor;
  • Version: a versão da API, no caso, v1;
  • Resource: o nome do recurso a ser acessado, no caso, produtos;
  • Id: item opcional, o identificador único do recurso, no caso, 25, usado quando necessário identificar diretamente o recurso;
  • Action: item opcional, a operação a ser executada por meio do recurso selecionado, no caso, adicionarAoCarrinho, podendo ser vinculado uma ou mais vezes, dependendo da estrutura da API e;
  • Parameter: item opcional, o parâmetro a ser passado à operação para a execução desta, no caso, 12, podendo se repetir quantas vezes forem necessárias.

Quando atingimos este nível de maturidade em uma API, estamos prontos para implementar o HATEOAS de forma plena e funcional. Então, com isso, chegamos ao nível máximo de maturidade que poderíamos atingir.

Nível 3 – HATEOAS

Assim como o nível 2 de maturidade, o HATEOAS faz uso de todas as suas ferramentas para garantir uma implementação mais eficiente de uma API RESTful, tornando-a mais abrangente e simples quando há a necessidade de uma integração ou do desenvolvimento de uma aplicação cliente. Implementar o HATEOAS implica em adicionar a uma resposta simples de uma consulta todas as informações de que o recurso dispõe, como o uso de verbos, operações e tudo o que for de responsabilidade daquele recurso.

Se pensarmos em um carrinho de compras, ao implementarmos o HATEOAS, uma simples consulta a ele, por meio do endpoint /api/vendor/v1/carrinho/12 teríamos a seguinte resposta:

É notável que, ao invés da complexidade de um objeto extenso, com todas as informações dispostas nele, o que temos com o HATEOAS é um objeto contendo apenas as informações mais básicas a respeito de um recurso, geralmente suas propriedades escalares, e as demais informações podem ser acessadas por outros endpoints que o próprio recurso disponibiliza. Assim, não precisamos incluir na resposta para o carrinho os itens constantes nele, mas podemos disponibilizar o endpoint para esta consulta.

Se observarmos bem, mesmo que este seja apenas um exemplo, podemos ver que nenhuma informação complexa é fornecida. Por exemplo, se colocássemos o cliente na resposta, teríamos que colocar seus endereços e informações de contato, o que tornaria o objeto de resposta complexo e com um payload mais alto que o realmente necessário. Com HATEOAS nós colocamos apenas o endpoint onde esta informação pode ser obtida. Quando obtivermos a informação sobre o cliente, o HATEOAS nos obriga a fornecer os endpoints que apontam para as demais informações e ações referentes ao recurso cliente que acabamos de obter e assim sucessivamente.

E o que eu faço com tudo isso?

Bom, como gosto de destacar sempre, usar ou não algo abordado aqui tem uma relação direta com o nível de maturidade da equipe envolvida com o desenvolvimento. O HATEOAS é um passo gigantesco para uma equipe que mal tenha atingido o nível 1 de maturidade de Richardson, mas pode ser um passo bem pequeno se a equipe já aplica o nível 2. Portanto, o que fazer com esta informação cabe a cada um decidir.

Mas, esteja certo de uma coisa: o HATEOAS até pode ser aplicado em projetos que atendam ao nível 2 de maturidade, mesmo que exija um volume maior de implementação, mas iniciar um novo projeto já aplicando HATEOAS é sempre o melhor caminho.

E o que vem depois?

Num momento futuro, aproveitando que já iniciamos uma série de artigos sobre APIs RESTful com Delphi e Datasnap, trarei uma implementação simples de uma API CRUD aplicando HATEOAS para simplificar as respostas e flexibilizar a camada de consumo.

Até a próxima!

Sobre Willian Tuttoilmondo 12 Artigos
Arquiteto de software com mais de 25 anos de experiência em desenvolvimento de software, especialista em desenvolvimento multicamadas utilizando Embarcadero Delphi e NodeJS para o back-end e o próprio Delphi para o front-end. Usuário Linux desde 1998, é evangelista PostgreSQL, banco de dados no qual é especialista. Ocupa hoje a posição de arquiteto de software na TOTVS, a maior empresa de tecnologia do Brasil, além de ser sócio fundador da LT Digital Labs, empresa especializada em desenvolvimento e treinamentos.

1 Trackback / Pingback

  1. Construindo APIs RESTful nível 3 com Delphi e Datasnap

Faça um comentário

Seu e-mail não será publicado.


*