Design de API Rest
Entenda uma série de boas práticas para sua API Rest
A sociedade evoluiu, a tecnologia evoluiu, por consequência a forma que nos comunicamos também mudou, isso também pode e deve ser aplicado a tecnologia.
Hoje API’s são a base para qualquer negócio informatizado, portanto são a base para qualquer empresa, por isso vamos abordar abaixo conceitos básicos sobre o padrão Rest, sobre o protocolo HTTP e uma série de boas praticas visando a construção de uma API Rest de alto nível.
Rest e Restful
Representational State Transfer (Transferencia de Estado Representacional) ou Rest, representa uma série de princípios definidos pela World Wide Web, visando a padronização de rotas, requisições e comunicações sem estado, o próprio protocolo HTTP, foi baseado nestas regras e se propõe a resolver todos estes problemas.
Restful é o termo atribuído ha uma API que possui a inteligência de aplicar o padrão Rest.
Protocolo HTTP
O protocolo HTTP é a base de tudo, as requisições chegam e saem através dele, os padrões são baseados em sua estrutura, e seus códigos de resposta, por esse motivo, todas as nossas API's devem respeita-lo e aplicar suas melhores práticas, portanto segue abaixo algumas delas:
Verbos do protocolo HTTP
Existem 9 diferentes verbos do protocolo HTTP, vamos conhecer abaixo para que server cada um:
A maioria das Api's utilizam apenas os verbos GET, POST, PUT e DELETE, porem isso não é uma regra, se fizer sentido para sua aplicação, utilize-as.
Padronização das rotas
Reduza a necessidade de documentações extensas, utilize paths legíveis ao olho humano, existem diversos tipos de padrões para as rotas, os mais utilizados estão exemplificados abaixo:
O ideal é que seja definido um padrão único para ser aplicado em todas as Api's da sua empresa, minha preferencia pessoal é utilizar o padrão no plural, portanto todos os exemplos deste post estarão seguindo esse padrão, segue abaixo um exemplo pratico cobrindo os principais verbos do protocolo HTTP:
Verbos do protocolo HTTP ao invés de operações
O exemplo a seguir demonstra rotas que possuem operações em seu nome, esse tipo de exemplo deve ser evitado
O correto é que as rotas sejam apenas substantivos que representem o domínio em si e utilizem os verbos do protocolo HTTP, conforme o exemplo abaixo:
Paths extensos
Evite Paths enormes, limite até no máximo 2 níveis de Paths aninhados ao principal, conforme o exemplo:
Padronização dos códigos de resposta
Se olharmos a tabela abaixo de códigos HTTP, vamos nos deparar com uma infinidade de códigos, implementa-los a risca se torna algo extremamente impossível, é um árduo trabalho tanto para quem constrói as Api's para quem as consome.
Para resolver este problema, geralmente cada desenvolvedor elege um grupo de códigos HTTP para utilizarem no desenvolvimento de suas API's, seguindo esta linha, recomendo o uso dos seguintes códigos:
HTTP 200 - OK
Geralmente esse é um código de retorno é utilizado como coringa, onde independente do tipo de requisição, se o processamento é realizado com sucesso, esse é o código de retorno default, porem eu recomendo utiliza-lo nos seguintes momentos:
- Requisições do tipo GET onde o dado de retorno não esta paginado, por exemplo uma consulta por um id.
- Requisições GET onde a API esta retornando a ultima pagina de um dado paginado.
- Requisições POST que realizem operações lógicas que não criam dados, por exemplo um cálculo.
HTTP 201- Created
Geralmente utilizado em requisições POST, onde algum dado foi criado em algum repositório.
HTTP 202 - Accepted
Geralmente utilizado em requisições POST, indica que algum dado foi recebido e seu processamento será realizado de forma assíncrona , por exemplo a API posta o dado em uma fila MQ e um worker tratará o dado em um segundo momento.
HTTP 204 - No Content
Geralmente utilizado em requisições PUT ou DELETE, indica que o processamento foi finalizado e a API não tem nenhum dado para devolver para seu cliente.
HTTP 206 - Partial Content
Geralmente utilizado em requisições GET onde o dado retornado esta paginado
HTTP 400 - Bad Request
Geralmente utilizado em todos os tipos de requisições, ocorre quando o cliente preencheu algum dado incorreto na requisição.
Geralmente este é um código coringa, quando sua API não tratar individualmente outros códigos 4XX, a mesma deve retornar o código 400.
HTTP 401 - Unauthorized
Geralmente é utilizado para qualquer tipo de requisição, ocorre sempre que um usuário não esta autenticado para uso da API.
HTTP 404 - Not Found
Esse código de resposta pode ser utilizado nos seguintes momentos:
- Pode ser utilizado para qualquer tipo de requisição, ocorre quando a aplicação cliente procura por uma rota/recurso que não existe
- Pode ser utilizado em uma requisição GET ou PUT, ocorre quando a aplicação client tenta consultar ou alterar um domínio que não existe
HTTP 408 - Timeout
Geralmente é utilizado para qualquer tipo de requisição, ocorre sempre que uma API gera timeout em uma das suas operações.
HTTP 409 - Conflict
Geralmente é utilizado em requisições do tipo POST, ocorre quando o cliente tenta criar um dado que ja existe.
HTTP 500 - Internal Server Error
Geralmente é utilizado para qualquer tipo de requisição, ocorre para qualquer tipo de erro de processamento da API.
Geralmente este é um código coringa, quando sua API não tratar individualmente outros códigos 5XX, a mesma deve retornar o código 500.
HTTP 502 - Bad Gateway
Geralmente é utilizado para qualquer tipo de requisição, ocorrerá sempre que uma dependência externa a API, apresentar algum tipo de comportamento inesperado.
Lembrando que os códigos que descrevi acima são os conjuntos de códigos que geralmente utilizo, porem isso não é uma regra, se fizer sentido para sua API, utilize outros códigos.
Versionamento
Existem diferentes modelos de versionamento de API, não existe nenhuma regra definida no protocolo HTTP, também não existe nenhum tipo de padrão que seja unanimidade pela comunidade, seguindo esta linha, sugiro que sua utilização esteja bem explicita para os clientes, com a versão no final do Path, por exemplo:
Uma pratica importante é garantir que o versionamento da API não fique diretamente em seu código fonte, deixe seu código fonte limpo, deixe essa responsabilidade com um Gateway de API, caso precise manter 2 diferentes versões da API no ar, tenha as diferentes versões publicadas em instancias diferentes da sua infraestrutura de produção.
Autenticação
Garantir a segurança dos dados também faz parte do desing de uma API, hoje o principal modelo utilizado pela comunidade é o Oauth 2.0, este é um padrão de autenticação, que foi construído para preservar os dados sensíveis de um usuário, minha recomendação seria sempre que possível utiliza-lo, porem caso não seja possível, não deixe sua API sem autenticação, no mínimo utilize uma autenticação do tipo basic, onde geralmente é criado um token em base64 a partir de um usuário e senha.
Stateless
Garanta que sua API não guarde nenhum tipo de estado referente a comunicação, toda requisição deve ser inteligente o suficiente para se resolver, sem armazenar quaisquer dados de seção.
Negociação de conteúdo
Apesar do ideal ser trafegar apenas JSON, caso sua API trafegue outros tipos de dados, por exemplo XML, utilize as chaves Accept e Content-Type do protocolo HTTP para detalhar todas as possibilidades
Paginação
Em requisições GET que retornem listas, devolva somente o necessário, pagine sua requisição, desta forma todos ganham, pois será trafegado somente o necessário, melhorando a experiencia do usuário final.
Não existe nenhum padrão amplamente utilizado pelo mercado para tal prática, por esse motivo sugiro que sua utilização esteja bem explicita para os clientes, por exemplo:
http://localhost:8080/api/customers?start=20&limit=5
No exemplo acima o campo start controla a partir de qual item deve se iniciar a consulta, ja o campo limit controla a quantidade máxima de registros na consulta.
Para uma paginação ser completa, não basta apenas paginar o request, precisamos também paginar de forma clara e explicita para os clientes o response, na pratica teríamos um retorno similar a este exemplo:
{
"metadata": {
"type": "list",
"start": 20,
"limit": 5,
"total": 51
},
"results": [
{
"id": 21,
"name": "Rishley Snyder",
"sex": "M",
"email": "rishley.snyder@hotmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1985-06-30",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 22,
"name": "Milo Mccurdy",
"sex": "M",
"email": "milo.mccurdy@yahoo.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1974-05-07",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 23,
"name": "Sammi Zepeda",
"sex": "F",
"email": "sammi.zepeda@gmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1956-12-29",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 24,
"name": "Romaine Dingman",
"sex": "F",
"email": "romaine.dingman@outlook.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1990-08-26",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 25,
"name": "Almeda Hack",
"sex": "F",
"email": "almeda.hack@hotmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1981-09-05",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
}
]
}
No exemplo acima, na estrutura principal do JSON temos os campos results que contempla o conteúdo da requisição e o campo metadata, detalhando os dados referente a paginação.
Ordenação
Otimize suas requisições, facilite ainda mais a vida de seus clientes, permita que sua Api, ordene sua resposta, desta forma o cliente pode não precisar realizar nenhum tipo de tratamento no dado.
Como no item anterior, não existe nenhum padrão amplamente utilizado pelo mercado para tal prática, por esse motivo sugiro que sua utilização esteja bem explicita para os clientes, por exemplo:
http://localhost:8080/api/customers?start=0&limit=5&sort=sex,date_birth&desc=date_birth
No exemplo acima o campo sort controla quais os campos devem ser ordenados separados por virgula, ja o campo desc controla quais os campos devem ser ordenados de forma decrescente, desta forma podemos considerar que a ordenação default é crescente.
Na solicitação ordenamos pelo campo sex e date_birth, ainda considerando o campo date_birth na forma decrescente, na pratica teríamos um retorno similar a este exemplo:
{
"metadata": {
"type": "list",
"start": 0,
"limit": 5,
"total": 51
},
"results": [
{
"id": 49,
"name": "Janna Ying",
"sex": "F",
"email": "janna.ying@yahoo.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1992-06-06",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 24,
"name": "Romaine Dingman",
"sex": "F",
"email": "romaine.dingman@outlook.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1990-08-26",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 7,
"name": "Ruthie Coco",
"sex": "F",
"email": "ruthie.coco@gmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1988-07-27",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 37,
"name": "Scarlet Barnwell",
"sex": "F",
"email": "scarlet.barnwell@hotmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1987-10-26",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
},
{
"id": 9,
"name": "Vinaya Justus",
"sex": "F",
"email": "vinaya.justus@hotmail.com",
"cel_ddi": "55",
"cel_ddd": "11",
"cel_number": "987654321",
"date_birth": "1986-04-18",
"register_date": "2018-09-06 22:32:51",
"last_update_date": null
}
]
}
Respostas Parciais
Otimize ainda mais suas requisições GET, permita seus clientes recuperarem somente os campos que realmente necessitam, desta forma de ponta a ponta serão trafegado apenas os dados necessários.
Como nos itens anteriores, não existe um padrão amplamente utilizado pela comunidade, por isso eu sugiro que seja algo bem explicito, para que facilmente os clientes de sua API consigam identificar, por exemplo:
http://localhost:8080/api/customers/1?fields=metadata[],results[][id,name,sex]
No exemplo acima o campo fields controla quais os campos devem ser retornados no retorno da requisição, separando os campos no nível principal por virgula e os campos aninhados através do [], na pratica teríamos um retorno similar a este exemplo:
{
"metadata": {
"type": "object",
"start": 0,
"limit": 0,
"total": 0
},
"results": {
"id": 1,
"name": "Willian da Silva",
"sex": "M"
}
}
Hateoas
Hateoas representa o termo Hypermedia as the engine of application State (Hipermídia como o mecanismo do estado do aplicativo), é um padrão que sempre inclui links uteis junto as respostas de uma requisição, por exemplo:
http://localhost:8080/api/customers?start=20&limit=2
Como nos itens anteriores, não existe um padrão amplamente utilizado pela comunidade, o único consenso, é que a informação fica centralizada no objeto links, por exemplo:
{
"metadata":{
"type":"list",
"start":20,
"limit":2,
"total":51
},
"results":[
{
"id":21,
"name":"Rishley Snyder",
"sex":"M",
"email":"rishley.snyder@hotmail.com",
"cel_ddi":"55",
"cel_ddd":"11",
"cel_number":"987654321",
"date_birth":"1985-06-30",
"register_date":"2018-09-06 22:32:51",
"last_update_date":null,
"links":{
"self":{
"href":"http://localhost:8080/api/customers/21",
"type":"GET"
},
"delete":{
"href":"http://localhost:8080/api/customers/21",
"type":"DELETE"
}
}
},
{
"id":22,
"name":"Milo Mccurdy",
"sex":"M",
"email":"milo.mccurdy@yahoo.com",
"cel_ddi":"55",
"cel_ddd":"11",
"cel_number":"987654321",
"date_birth":"1974-05-07",
"register_date":"2018-09-06 22:32:51",
"last_update_date":null,
"links":{
"self":{
"href":"http://localhost:8080/api/customers/22",
"type":"GET"
},
"delete":{
"href":"http://localhost:8080/api/customers/22",
"type":"DELETE"
}
}
}
]
}
Os principais benefícios de seguir essa abordagem são:
- As aplicações clients conhecem facilmente o limite e próximos passos de cada domínio
- As aplicações clients podem recuperar seus paths dinamicamente sem a necessidade de colocar-los fixo no código
- Os desenvolvedores da API podem alterar o esquema de paths sem quebrar as aplicações clients
Open Documentation
Alem de seguir as melhores práticas, faça ainda mais que isso, utilize frameworks como o Swagger, que é um framework open source que apoia o desenvolvedor no desenho, documentação e especificação de suas Api's.
A principal vantagem do Swagger é que o mesmo possui uma dependência para as principais linguagens de programação do mercado, e funciona basicamente lendo as anotações do código, o que facilita muito que a cada alteração no código, sua documentação permaneça atualizada.
Uma outra grande vantagem do Swagger é que a partir de sua especificação, é possível gerar clients para consumo das Api's, diminuindo a complexidade das integrações e o esforço para desenvolvimento.
Veja abaixo um exemplo de uma API com Swagger:
Conclusão
Se você leu atentamente todos os itens citados acima, você ja tem a total certeza que para construir uma API Rest de alta qualidade, não basta apenas conhecer uma linguagem de desenvolvimento, é preciso conhecer muito bem o protocolo HTTP e um conjunto de padrões que com certeza elevam o nível de sua API.
Referencias bibliográficas
Projetando uma API Rest — https://blog.octo.com/pt-br/projetando-uma-api-rest/
Guia para projeto API HTTP — https://github.com/Gutem/http-api-design/
Rest Api Desing — Resource Modeling — https://www.thoughtworks.com/pt/insights/blog/rest-api-design-resource-modeling
Richardson Maturity Model — https://martinfowler.com/articles/richardsonMaturityModel.html
RFCs do Protocolo HTTP — https://tools.ietf.org/html/rfc2616
https://tools.ietf.org/html/rfc7540
https://tools.ietf.org/html/rfc7541Introdução ao HTTP/2 — https://developers.google.com/web/fundamentals/performance/http2/