Escolar Documentos
Profissional Documentos
Cultura Documentos
com/br/articles/nivelando-sua-rest-api
Não irei traçar comparações entre REST e SOAP e muito menos contar a história da REST e do
protocolo HTTP, pois esse conteúdo é abundante na internet. O propósito desse artigo é aplicar e
passar pelos níveis de maturidade, entendendo os problemas que eles resolvem, e apresentando
exemplos intuitivos.
Segundo Roy Fielding (criador do conceito REST) sua API só pode ser chamada de REST API
quando for HATEOAS, ou seja, quando estiver no terceiro nível de maturidade.
"O que é necessário para tornar o estilo de arquitetura REST claro sobre a noção de que o
hipertexto é uma restrição? Em outras palavras, se o mecanismo do estado do aplicativo (e,
portanto, a API) não estiver sendo controlada pelo hipertexto, ela não poderá ser RESTful e não
poderá ser uma API REST."
Tendo essa premissa, iremos resolver o problema ao ponto que nossa API possa ser considera
uma REST API. Problema:
Os quadrados definem o agrupamento de informação no caso, customers, orders, products etc. E as linhas
definem o tipo de relação que um tem com o outro; esses são os pontos essenciais e que devem estar
representados em uma API.
Nível 0 de maturidade
Nesse nível tende-se a usar somente o POST como verbo e utilizar apenas uma URI,
combinando com uma variedade de comandos próprios.
A api do Flickr é um exemplo clássico desse nível. Por este motivo a necessidade de uma
documentação se torna emergente visto que os comandos não são padrões, os status de
retornos também não são utilizados, com isso se perde o poder da predição, onde conhecendo o
estilo e o protocolo, tem-se um entendimento base do funcionamento da API.
Veja um exemplo:
POST http://mycompany.com?list.products
POST http://mycompany.com?create.product
POST http://mycompany.com?delete.order=1
...
É interessante observar que para operações simples se faz necessário uma documentação
robusta, pois além da falta de clareza sobre a estrutura e suas relações, os comandos são
personalizados, como por ex.: list.products, create.product etc.
Esse é o nível que demanda mais atenção, em minha opinião. O recurso é uma forma de
organizar conjuntos de informações coesas. Ele delimita fronteiras entre um tema e o outro. Essa
é o "Gênesis da API", pois é onde tudo começa.
"A abstração chave de informação no REST é um recurso. Qualquer informação que possa
receber um nome pode ser um recurso: um documento, uma imagem, um serviço (por exemplo,
"clima de hoje em Los Angeles"), um objeto não virtual (uma pessoa, por exemplo), uma coleção
de outros recursos e assim por diante. Em outras palavras, qualquer conceito que possa ser alvo
de uma referência deve se encaixar na definição de recurso."
Entende-se que qualquer conceito que possa ser alcançado a partir de um link pode ser
modelado como recurso. A grosso modo seria como modelar as tabelas em um banco relacional.
Cada recurso deve ter uma estrutura e uma URI que será usada para identificá-lo. Recursos não
devem ser uma cópia exata de suas tabelas no banco de dados ou do seu modelo de objetos, ele
deve ser a forma como você quer que o cliente entenda sua estrutura de informação,
independente da técnica aplicada no backend. Entendido isso, vamos implementar a API.
No exemplo abaixo temos uma URI que identifica o recurso "products", e esse recurso é
composto pelo id, nome e valor.
POST http://mycompany.com/products/1
Reposta:
Content-Type: application/json
{
"id": 1,
"name": "MacBook Pro",
"price": 10.000
}
Assim teremos uma URI para cada recurso da nossa API, como no ex.:
POST /customers/1
POST /customers/1/orders
POST /customers/orders/1/order-items
POST /products/1
POST /packages/1
O verbo, a exemplo do nível zero, continua sendo o POST.
Manter o nome do recurso no plural é uma boa prática. Isso se justifica se pensarmos em
recursos como pensamos em diretórios, e assim podemos dizer que o diretório customers tem um
arquivo com uma estrutura composta por id, nome e sexo e o identificador(nome do arquivo) é 2,
por ex.:
Ao buscar um cliente:
Resposta:
Content-Type: application/json
{
"id": 2,
"name": "Adam Smith",
"gender":"M"
}
Fica claro que ao chamar /customers irá retornar todos os recursos (arquivos) de dentro do
diretório clientes.
Recurso raiz
Um produto, assim como cliente, é um recurso raiz. Um recurso raiz existe independente de outro
recurso. Ele aparecerá após o domínio da aplicação.
POST http://mycompany.com/customers
POST http://mycompany.com/products
POST http://mycompany.com/packages
...
Isso significa que posso criar um cliente, produto ou pacote independente de qualquer outro
recurso.
Um sub recurso existirá após a criação do seu antecessor. Pedidos não existe sem um cliente
associado a ele, ou seja, o recurso pedidos vive em função de um cliente e por esse motivo
pedidos deve estar abaixo de cliente para evidenciar esse fato.
Baseado na
imagem acima podemos fazer uma solicitação para recuperar todos os pedidos do cliente 2:
POST /customers/2/orders
Resultado:
Um recurso não pode aparecer em lugares distintos com o mesmo nome, pois isso traria, uma
certa nebulosidade sobre a API.
No nosso problema temos produtos e pacote de produtos, em um pacote podemos ter vários
produtos, para vendê-los como combo. Agora queremos saber quais os produtos estão em um
pacote. Baseado no que vimos até aqui talvez isso fizesse sentido:
POST /packages/2/products
Porém não podemos fazer isso porque produtos é um recurso que já existe em outro ponto da
API.
Qualificando relações
Toda vez que uma relação estiver em evidência, por exemplo: for necessário executar operações,
ou for acessível por um link (nível 3 de maturidade), essa relação deverá ser representada.
Para adicionar um produto em um pacote a operação não será sobre o produto, nem sobre o
pacote e sim sobre a relação que existe entre os dois.
POST /packages/1/products-relationships
Resultado:
O "products-relationships" não precisa ser mais uma tabela no banco ou mais um objeto no
domínio porque esses dois mundos têm suas próprias regras.
Qualificando operações
Existem situações onde podemos abstrair uma complexidade de negócio para não ter regras
distribuídas pelos clientes que consomem a API.
Imagine que que ao alterar o preço de um pacote eu tenha que enviar um e-mail, notificando o
departamento de vendas e solicitar a aprovação do novo preço.
O cliente da API poderia ser informado que, após alterar o preço do pacote, deve enviar um e-
mail para o departamento de vendas e solicitar a aprovação do novo preço - toda vez que essa
alteração ocorrer.
O problema dessa abordagem é que quando a regra de negócio mudar (ex.: deve enviar um e-
mail para o departamento de compras), todos os clientes da API deverão ser alterados para
implementar a nova regra de negócio; imagine com 10 clientes ou mais, todos eles deverão ser
alterados para a nova regra, isso costuma ser custoso e demorado.
O outro ponto está relacionado a informações adicionais como por ex.: quem aprovou a alteração
do preço do pacote?
Para resolver esse problema e evitar entregar a regra de negócio para o cliente da API podemos
representar esse conceito em forma de recurso, da seguinte forma:
POST /packages/1/change-of-prices
Resultado:
]
Nesse caso temos duas solicitações de alteração de preço para o pacote 1. A alteração solicitada
em 10/05/2018, foi aprovada pelo aprovador identificado pelo id 20, e também tem uma
justificativa da aprovação.
A solicitação do dia 12/08/2018, ainda está pendente de aprovação. Dessa forma conseguimos
manter a regra no backend, a operação não precisa ser síncrona e mantemos um histórico de
alterações de preços (esse modelo é muito aderente ao padrão CQRS).
Com uma representação do processo consigo adicionar informações relevantes como
comentário, aprovador e o que mais for necessário.
É sempre importante pensar sobre o uso do PUT e do PATCH (nível 2) existem alterações que
são mais complexas do que somente alterar o valor, como exposto no exemplo anterior.
Obs: como não temos links hypermedia nesse nível, o "approverId" foi colocado no corpo do
recurso, iremos resolver isso quando falarmos de HATEOAS.
Nomeando os recursos
O ponto mais importante aqui é a consistência, use o mesmo padrão para toda a API.
A RFC define a formação de uma URI. O mais importante para nós segue abaixo:
POST /customers/2/orders (sem letras maiúsculas)
É comum aparecer nesse nível recursos como operações que já existem no protocolo. Ex.:
POST /customers/2/update
POST /customers/create
POST /customers/2/delete
...
Essa estrutura não está representando um update ou create, está sendo usado como um verbo.
Esses verbos já existem no protocolo HTTP, então não faça isso.
Essa é outra forma equivocada do uso dos recursos. Usá-los para transformar dados não é a
forma adequada para tal finalidade. Ex.:
POST /customers/2/xml
POST /customers/2/json
...
Existe à maneira REST de negociar o formato dos dados e isso pode ser feito usando content
negotiation.
Extensão como agente transformador
É comum alguns frameworks e desenvolvedores usarem extensões para indicar o tipo do dado
que deverá ser retornado.
POST http://mycompany.com/customers/2.json
POST http://mycompany.com/customers.xml
...
Essa não é a forma adequada para esse tipo de operação. Como na solução anterior o content
negotiation poderia ser usado.
Esse nível apresenta um conjunto de verbos que representam operações possíveis sobre um
recurso, no primeiro nível definimos os substantivos e nesse nível iremos aplicar os verbos. Os
principais verbos HTTP são: GET, PUT, POST, DELETE, HEAD, PATCH, OPTIONS.
No nosso sistema de vendas, entendendo que temos o recurso clientes, préviamente sabemos
que podemos alterar, ler, remover, criar um recurso. Fazendo uma analogia com um banco de
dados relacional seriam as operações que podem ser executadas sobre um tabela, como select,
update, delete etc.
PUT ou PATCH? A forma de uso do verbo PUT é sempre enviando todos os atributos no payload,
no caso do recurso "customers" seria necessário enviar sexo, mesmo querendo alterar somente o
nome. O contraponto seria o PATCH onde você envia somente a informação que quer alterar.