Você está na página 1de 8

https://www.infoq.

com/br/articles/nivelando-sua-rest-api

Nivelando sua Rest API


O estilo arquitetural REST anda de mãos dadas com o protocolo HTTP, porém não se limita a ele,
contudo trataremos sobre REST, HTTP e JSON em nossos exemplos por se tratar de uma
combinação comum.

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.

O modelo de Leonard Richardson divide a implementação em 4 níveis de maturidade, partindo do


nível zero e indo até o nível 3. Iremos elaborar um problema e assim aplicaremos cada um dos
níveis, dessa forma ficará claro quais os problemas que cada nível resolve.

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.

Padrões internos possui problemas como a falta de documentação ou documentação


desatualizada ao ponto que se perde o conhecimentos sobre a API se os idealizadores não
fizerem mais parte do projeto. No final sobrará somente o código fonte e algumas horas perdidas
para tentar entender o que foi pensado.

Documentar é um processo fundamental, porém somente o necessário; se você utilizar o HTTP


como especificado, não será necessário documentar algo que já está documentado, e o mesmo
se aplica ao REST.

Nível 1 de maturidade - Recursos

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.

Roy Fielding define recurso da seguinte forma:

"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.

Nome dos recursos no plural

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:

POST /customers/2(diretório clientes e arquivo 2)

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.

Sub recurso - Hierarquia

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:

HTTP/1.1 200 OK Content-Type: application/json


[{
"id": 20,
"total": "10.000",
},
{
"id": 23,
"total": "2.000",
}
]
Dessa forma fica evidente que não existirá, no sistema, um pedido que não esteja associado a
um cliente.

O mesmo recurso não pode aparecer em lugares distintos

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.

Assim como usamos os substantivos para qualificar um agrupamento de informação (produtos,


clientes, etc) podemos fazer o mesmo para as relações.

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.

O exemplo abaixo mostra a representação dessa relação:

POST /packages/1/products-relationships
Resultado:

HTTP/1.1 200 OK Content-Type: application/json


[{
"id": 1,
"productId":2
},
{
"id": 2,
"productId":3
}
]
Nesse caso o pacote 1 está relacionado ao produto 2 e 3. Ainda não está bom porque não temos
ferramentas suficientes nesse nível. Iremos melhorar isso no nível 2 e 3.
Assim como em um banco relacional onde você precisa de uma tabela intermediária para ligar
duas entidades, no caso de pacotes e produtos, com REST você também precisará de um
recurso intermediário para relacioná-los. Entretanto isso só será modelado dessa forma se você
precisar executar operações sobre a relação, caso contrário é desnecessário.

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:

HTTP/1.1 200 OK Content-Type: application/json


[
{
"id":2,
"value":150.00,
"status": "PENDING",
"requestDate": "20/08/2018"
},
{
"id":1,
"value":200.00,
"status": "APPROVED",
"requestDate": "10/05/2018",
"comments": "approved for the mother's day campaign"
"approverId": 20
}

]
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)

POST /products/2/basic-categories (use o hífen para separações)

Recurso como operação existente

É 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.

Recurso como agente transformador

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.

Finalizamos o nível 1 de maturidade, com isso organizamos os dados e adicionamos um nível de


relacionamento entre eles (hierarquias). Com tudo organizado podemos entender quais
operações poderão ser executada sobre os recursos e a relação entre eles nos próximos tópicos.

Nível 2 de maturidade - Verbos HTTP

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.

POST /customers (criar)


GET /customers/20 (buscar)
PUT /customers/20 (atualizar)
PATCH /customers/20 (atualizar parcialmente)
DELETE /customers/20 (remover)
HEAD /customers/20 (verificar se existe)
OPTIONS /customers/20 (operações possíveis)
Não irei explicar cada método e seus respectivos retornos HTTP, porque esse conteúdo há em
abundância na internet, porém farei algumas observações que considero relevantes.

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.

Você também pode gostar