APIs e Microsserviços

16 out, 2019

Como chegar ao nível supremo de APIs RESTful – Parte 8

Publicidade

Oi amigos hackers, sou eu aqui de novo, e agora, chegamos ao último artigo da nossa série sobre APIs…

Se você não leu os últimos artigos, recomendo, caso contrário, talvez você fique por fora de algum assunto que vamos abordar aqui.

Nesse artigo, vamos entender como chegar ao nível supremo de experiência em APIs RESTful.

A ideia agora é exemplificar:

O que não fazer e o que fazer na sua API

Para isso, vamos utilizar um framework chamado “Richardson Maturity Model” com uma pequena modificação, que é a inclusão do nível 4 (Developer Experience).

No alt text provided for this image

Nível 1

Esse nível é tão básico que nem vou perder muito tempo nele.

Nesse primeiro nível você já está sabendo utilizar os recursos de forma individual, ou seja, cada URL com uma responsabilidade e um objeto específico, porém você ainda não sabe como utilizar os métodos HTTP e outras funções mais avançadas.

Em outras palavras, você só está utilizando o REST como mecanismo de transporte, e não se importa com boas práticas, e coisas do tipo.

Nível 2

No nível 2 você já entende um pouco mais sobre REST, e por isso já se preocupa em utilizar a especificação do HTTP através de métodos, headers, códigos de status, e paginação.

Não use verbos na URL

O protocolo HTTP já tem seus próprios métodos, então você não precisa colocar verbos na URL, não é mesmo?

// Não Recomendado 
URL mediumgameapi.com/getaccounts
// Recomendado GET mediumgameapi.com/accounts PUT mediumgameapi.com/accounts POST mediumgameapi.com/accounts DELETE mediumgameapi.com/accounts
Use Content-Type no header

Eu sei que ainda é muito comum utilizar o formato dos dados na URL via extensão, mas existe um header específico para isso.

// Não Recomendado
GET /entities/123/payable_accounts.json 

// Recomendado 
GET /entities/123/payable_accounts
Accept: application/json
Content-Type: application/json

Tenha uma hierarquia simples na URL

Se você fizer uma navegação com mais de três níveis de hierarquia, isso pode complicar a vida de quem utiliza a sua API. Portanto, se tiver um cenário muito complexo de navegação de objetos, use a combinação com Query String.

// Não Recomendado
/accounts/00928376/transaction/8738903/zip/6500/city/dublin/state/ca
// Recomendado 
/accounts/00928376/transaction/8738903?zip=6500&city=dublin&state=ca

Utilize status code HTTP pra tudo

Essa eu não precisava nem falar, mas vamos lá… Se você quer proporcionar uma comunicação padronizada e que utiliza as melhores práticas, então você precisa utilizar os códigos de status padrões do protocolo HTTP.

Se você quer saber mais sobre o tema, veja meu artigo sobre.

Se quiser saber mais sobre esse tema, recomendo a leitura do artigo (Parte 2 — O que você precisa saber sobre métodos e códigos de status do protocolo HTTP)

Paginação e filtros

Para proporcionar uma boa experiência em termos de performance e busca de dados, então você precisa tomar os seguintes cuidados:

  • Primeiro de tudo, implementar alguma técnica de paginação, se ainda não sabe como, eu expliquei sobre isso em um dos artigos da série.
  • Sempre especificar na documentação em qual página inicia uma busca, exemplo: 0 ou 1, pois se você não especifica, o consumidor pode utilizar qualquer uma e obter o mesmo resultado, confuso não?
  • Disponibilizar ranges de datas no filtro, como por exemplo: CreatedBefore, CreatedAfter.
  • Não colocar filtros obrigatórios que anulam o objetivo da busca, exemplo imagem abaixo:
No alt text provided for this image

Notem que a API acima é para buscar tickets, mas como parâmetro obrigatório ela solicita o idRequester, o que anula diretamente o objetivo principal do endpoint e prejudica a experiência do consumidor da API.

Se quiser saber mais sobre esse tema, recomendo a leitura do artigo (Parte 5 — Paginação, Ordenação e Filtros em APIs RESTful)

Nível 3

No nível 3, você já está implementando questões mais avançadas como versionamento, rastreabilidade e HATEOAS.

Outra característica desse nível é que você já tem uma certa preocupação com a experiência do developer que utiliza sua API, e por isso, faz o uso de padrões universais, e cria objetos pensando em legibilidade.

Versionamento

Existem diversas técnicas de versionamento de APIs, eu tenho também um artigo específico sobre isso (é importante você leia).

Para exemplificar o assunto, abaixo quero comparar dois modelos distintos de versionamento:

// Não Recomendado - API da Salesforce 
// 
yourInstance.salesforce.com/services/data/
[{ "version" : "20.0", "label" : "Winter '11", 
"url" : "/services/data/v20.0"}, 
{"version" : "21.0", "label" : "Spring '11", 
"url" : "/services/data/v21.0"}]
// Recomendado - API do Stripe // api.stripe.com/v1/charges -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: -H "Stripe-Version: 2019-03-14"

No primeiro exemplo (API Salesforce), podemos notar que tanto a versão quanto a subversão ficam na URL, o que dificulta bastante a navegação da API. Além disso, podemos notar uma quantidade enorme de versões, como por exemplo versão 21. Isso de certa forma pode até “assustar” o desenvolvedor, que ao começar a desenvolver algo, ele nota que as mudanças são constantes.

No segundo exemplo (API Stripe), a versão fica na URL, porém a subversão fica no header. Nesse modelo, a explorabilidade da API se mantém intacta, além de parecer muito mais organizada, e o mais importante, proporciona uma melhor experiência ao developer.

Se quiser saber mais sobre esse tema, recomendo a leitura do artigo (Parte 4 — Versionando APIs RESTful)

Objetos legíveis e limpos

A legibilidade, ou seja, a facilidade de leitura de um objeto pode parecer individual, mas na prática não é. Quanto mais simples for um objeto, mais fácil é entendê-lo, portanto não utilizem estruturas desnecessárias, como:

// Não Recomendado
//
GET /entities/:entity_id/receivable_accounts?per_page=15&page=3
{
"receivable_account": { 
"id": 1,
"entity_id": 1,
"status": 1,
"status_name": "unreceived",
"due_date": "2011-09-10",
"occurred_at": null,
"amount": "100.0",
"ticket_amount": null,
"interest_amount": null,
"discount_amount": null,
"nominal_amount": null
  }
}

Notem que no exemplo acima, eu já estou fazendo uma requisição ao recurso receivable_account, portanto quando eu retorno o resultado, eu não preciso repetir ele novamente na estrutura de retorno, pois isso só dificulta o utilização do consumir da API.

Gere rastros através de logs e timestamping

Tão importante quanto você permitir uma boa experiência em manipulação de dados na sua API, é você manter uma governança.

Para isso use padrões de rastreabilidade que permita você saber quem fez a criação ou alteração de um dado, como por exemplo:

{ 
"createdAt": 1501869101896, 
"createdBy": "Thiago", 
"updatedAt": 1501869101896, 
"updatedBy": "Thiago" 
}

Use sempre o padrão ISO 8601 para data e hora

Esse é um assunto chato e que quase nenhum desenvolvedor gosta. Formatação de data é sempre um “caos” no mundo de desenvolvimento, então não reinvente a roda, e use padrões para tratar isso. Utilize sempre o ISO 8601 em UTC.

{"createdAt":"2019-07-08T18:02:24.343Z"}

Use o HATEOAS — Hypermedia As The Engine Of Application State

Um dos pontos altos do padrão REST é permitir HATEOAS. Para quem não sabe o que é, é basicamente uma forma de retornar em um recurso links navegáveis para objetos relacionados.

Na prática, isso facilita muito o lado do client, pois pense no desenvolvimento frontend. Com uma API que tem HATEOAS você conseguiria fazer um binding dinâmico de um botão através do link. Veja o exemplo:

GET https://api.github.com/users/codemazeblog 
{ 
"login": "CodeMazeBlog", 
"id": 29179238, 
"avatar_url":"https://avatars0.githubusercontent.com/u/29179238?v=4", 
"url": "https://api.github.com/users/CodeMazeBlog", 
"html_url": "https://github.com/CodeMazeBlog", "followers_url":"https://api.github.com/users/CodeMazeBlog/followers", 
...

Nível 4

O nível 4 é o “supra sumo” de uma API RESTful, e nesse nível, além de entregar uma API funcionando e estável, você estará entregando uma ótima experiência pro developer que irá consumir a sua API.

Pense que aqui você já implementou o protocolo HTTP e os padrões REST nas suas melhores práticas, e que agora o desafio está nos detalhes que impactam na experiência de navegação e consumo.

Use conceitos de Developer Experience

Você já deve ter ouvido falar bastante sobre UX ou experiência do usuário, certo?

A disciplina de UX tem basicamente a responsabilidade de garantir uma boa experiência do usuário em relação a um produto ou serviço.

Porém, isso normalmente é esquecido quando pensamos no desenvolvedor como usuário. Talvez, por uma falsa impressão de que nós desenvolvedores gostamos de tela preta ou algo do tipo.

O fato é que quando falamos de uma API, também criamos uma experiência a partir dela, e para que a experiência seja positiva devemos aplicar conceitos de DX ou Developer Experience.

O DX é uma junção entre princípios de desenvolvimento com princípios de User Experience:

No alt text provided for this image

Como esse tema é extenso, vou compartilhar apenas as preocupações básicas de quem aplica esse conceito:

  • Task-Invocation Ratio: Quantas chamadas na API são necessárias para um desenvolvedor atingir seu objetivo?
  • Estrutura: Qual a estrutura de um request e de um response? Quão profundo tecnicamente o desenvolvedor precisa navegar para conseguir chegar no dado que ele quer utilizar?
  • Navegação: Quão complexo é navegar entre recursos na sua API?
  • Stack Size: Quantas ferramentas e bibliotecas o desenvolver precisa instalar para usar sua API?
  • Tempo do primeiro request: Em quanto tempo um desenvolvedor consegue fazer o primeiro request na sua API?
  • Error Handling: Qual a natureza dos possíveis erros? Quão difícil é resolver?

Se você quiser saber mais sobre esse assunto, veja este artigo sobre DX.

Documente as propriedades dos seus recursos

Ter uma boa documentação é básico quando falamos do nível 4, e não sei por qual motivo, mas vejo muitas documentações que partem do princípio de que os desenvolvedores conseguem adivinhar o que significa cada propriedade, por isso documente as propriedades do seu recurso, ou seja, descreva com detalhes a estrutura de dados de um request e um response. Veja a diferença na prática:

Não recomendado

No alt text provided for this image

Recomendado

No alt text provided for this image

Cuidado com limites de requisições

Se a sua API não fornece algo inteligente para tratar dados em massa (bulk data) tome bastante cuidado com o limite de requisições na sua API.

Eu sei que é muito importante ter políticas para ter uma infraestrutura mais saudável e escalável, mas isso não pode prejudicar a experiência de um desenvolvedor. Por isso, você não pode exagerar nesse limite.

Por exemplo: Eu já vi diversas APIs com rate-limit de 20 requisições por minuto e com uma paginação máxima de 100 itens por vez.

Agora imagine você consumindo uma API com essas limitações e tendo que construir um BI. Muito provavelmente, você irá se irritar com esse limite tão baixo na hora de criar um mecanismo de paginação.

Minha recomendação é de pelo menos um rate-limit de 40 requisições por minuto, mas isso é um feeling do que tenho visto como boa experiência no mercado, e isso pode variar de acordo com o tipo da API.

Se quiser saber mais sobre esse tema, recomendo a leitura do artigo (Parte 6 — Políticas em APIs RESTful)

Tenha uma API event-driven

Esse aqui é bem útil para quem quer ter uma infraestrutura eficiente no tráfego de dados, além de proporcionar uma boa experiência ao client.

Normalmente quando você vai consumir uma API e precisa dos dados atualizados, você tem que ficar fazendo requisições (pooling) o tempo todo para entender se existem dados novos ou não no servidor.

Fazendo um paralelo, é quase como perguntar para um vendedor da sua empresa o tempo todo: “Tem venda nova hoje?”

Agora pense na hipótese de que ao invés de você perguntar o tempo todo ao vendedor, quando ele fizer uma nova venda, ele mesmo vai te notificar proativamente.

Funciona exatamente assim uma API event-driven, ela tem subscribers, ou seja, clients que se inscrevem para serem notificados, e em caso de novos eventos, ela notifica automaticamente todos os clients inscritos.

No alt text provided for this image

Para ter uma API event-driven você deve implementar webhooks na sua API.

Mensagens de erro dev-friendly

Existem diferentes abordagens nesse assunto, mas aqui o objetivo é retornar mensagens friendly de erros na sua API, diminuindo a curva de diagnóstico de problemas.

Para isso, você apenas precisa customizar as respostas da sua API com uma estrutura de erros mais organizada e detalhada.

Vejam a minha recomendação (API Twilio):

GET https://api.twilio.com/2010-04-01/Accounts/1234/IncomingPhoneNumbers/1234 
Response: Status Code 404 
{ 
"RestException": { 
"Code": "20404", 
"Message": "The requested resource /2010-04-01/Accounts/1234/IncomingPhoneNumbers/1234 was not found", 
"MoreInfo": "https://www.twilio.com/docs/errors/20404", 
"Status": "404"
  }
}

Notem que interessante essa estrutura de response, além de retornar o status code do HTTP 404, ela te dá um código interno do erro para você navegar na doc, e um link detalhado do erro, com as possíveis soluções.

De fato, isso impacta positivamente a experiência do developer que irá consumir a API.

Ambiente de desenvolvimento (sandbox)

Se você quer dar possibilidade de testarem rapidamente algo na sua API, então você deve disponibilizar um ambiente para desenvolvimento e testes, mais chamado de sandbox.

Um ambiente sandbox simula basicamente um ambiente de produção, entretanto, por não ser o ambiente de produção, o desenvolvedor consegue fazer testes mais avançados, e operações sem receios de errar.

__________________________________________________________________________

Meus hackers favoritos, infelizmente chegamos ao fim dessa série de 8 artigos sobre APIs RESTful…

Espero que tenham aprendido bastante com esses conteúdos, e que com isso, consigam me ajudar a colocar o Brasil na lista dos melhores ecossistemas de APIs do mundo =)

Se você foi impactado por essa série, meu único pedido é que compartilhem para todos os developers que vocês conheçam.

E vamos juntos, rumo a um mundo melhor de APIs!

Valeu, e até a próxima série.