História sobre como criar uma API

Certa vez, ajudei um amigo que precisava integrar dados sobre moradias gratuitas e ocupadas do sistema de gerenciamento de propriedades no site do cliente. Para minha alegria, este sistema tinha uma API. Mas, infelizmente, foi muito mal organizado.

imagem

Decidi escrever este artigo não para criticar o sistema que será discutido, mas para falar sobre quais erros foram encontrados durante o desenvolvimento da API e sugerir maneiras de corrigir esses erros.

Visão geral da situação


A organização em questão usou o sistema Beds24 para gerenciar o espaço de vida. As informações sobre o que é gratuito e o que está ocupado foram sincronizadas com vários sistemas de reserva de acomodações (como Booking, AirBnB e outros). A organização estava envolvida no desenvolvimento do site e queria que a pesquisa exibisse apenas informações sobre as salas disponíveis por um período especificado e com capacidade adequada. Essa tarefa parecia muito simples, pois o Beds24 fornece uma API para integração com outros sistemas. De fato, os desenvolvedores dessa API cometeram muitos erros no design. Proponho analisar esses erros, identificar problemas específicos e falar sobre como abordar o desenvolvimento da API nas situações em consideração.

Problema nº 1: solicitar formato do corpo


Como o cliente está interessado apenas em informações sobre, digamos, que um quarto de hotel está livre ou ocupado, estamos interessados ​​apenas em nos referir ao /getAvailabilities API /getAvailabilities . E, embora uma chamada para essa API deva levar a dados sobre a disponibilidade de salas, na verdade, essa chamada parece uma solicitação POST, pois o autor da API decidiu equipá-la com a capacidade de aceitar filtros como um corpo JSON da solicitação. Aqui está uma lista de possíveis parâmetros de consulta e exemplos dos valores que eles aceitam:

 {   "checkIn": "20151001",   "lastNight": "20151002",   "checkOut": "20151003",   "roomId": "12345",   "propId": "1234",   "ownerId": "123",   "numAdult": "2",   "numChild": "0",   "offerId": "1",   "voucherCode": "",   "referer": "",   "agent": "",   "ignoreAvail": false,   "propIds": [       1235,       1236   ],   "roomIds": [       12347,       12348,       12349   ] } 

Vamos percorrer esse objeto JSON e falar sobre o que há de errado aqui.

  1. As datas ( checkIn , lastNight e checkOut ) estão no formato YYYYMMDD . Não há absolutamente nenhuma razão para não usar o formato ISO 8601 padrão ( YYYY-MM-DD ) ao converter datas em seqüências de caracteres, pois esse é um padrão amplamente usado para a apresentação de datas. É familiar para muitos desenvolvedores, é o que muitos analisadores JSON esperam receber na entrada. Além disso, há a sensação de que o campo lastNight é redundante, pois há um campo checkOut , que é sempre representado por uma data um dia antes da data especificada em lastNight . Em conexão com as deficiências observadas acima, sugiro que, ao projetar essas APIs, se esforce para sempre usar métodos padrão de apresentação de datas e tente não sobrecarregar os usuários da API com a necessidade de trabalhar com dados redundantes.
  2. Todos os campos identificadores, bem como os numChild e numChild , são numéricos, mas são representados como seqüências de caracteres. Nesse caso, não há motivo aparente para representá-los como cadeias.
  3. Aqui você pode observar os seguintes pares de campos: roomId e roomIds , assim como propId e propIds . A presença dos propId e propId é redundante, pois ambos podem ser usados ​​para transmitir identificadores. Além disso, há um problema com os tipos. Observe que o campo roomId é um campo de seqüência de caracteres e os valores numéricos dos identificadores devem ser usados ​​na matriz roomIds . Isso pode causar confusão, problemas com a análise e, além disso, sugere que no servidor algumas operações sejam executadas com cadeias e outras com números, apesar do fato de que essas cadeias e números são usados ​​para representar o mesmo dados.

Gostaria de convidar os desenvolvedores de API para tentar não complicar a vida daqueles que irão usar essas APIs, enquanto cometem erros como APIs ao criar APIs. Ou seja, deve-se buscar a formatação padrão dos dados, para garantir que eles não sejam redundantes, para garantir que diferentes tipos de dados não sejam usados ​​para representar entidades homogêneas. E não vale tudo, indiscriminadamente, para apresentar na forma de linhas.

Problema nº 2: formato do corpo da resposta


Como já mencionado, estamos interessados ​​apenas no /getAvailabilities API /getAvailabilities . Vamos ver como é a resposta desse endpoint e falar sobre quais deficiências foram feitas durante sua formação. Lembre-se de que, ao acessar a API, estamos interessados ​​em uma lista de identificadores de objetos gratuitos por um determinado período e que podem acomodar um determinado número de pessoas. Abaixo está um exemplo do corpo da solicitação para a API e um exemplo do que ela retorna em resposta a essa solicitação.

Aqui está o pedido:

 {   "checkIn": "20190501",   "checkOut": "20190503",   "ownerId": "25748",   "numAdult": "2",   "numChild": "0" } 

Aqui está a resposta:

 {   "10328": {       "roomId": "10328",       "propId": "4478",       "roomsavail": "0"   },   "13219": {       "roomId": "13219",       "propId": "5729",       "roomsavail": "0"   },   "14900": {       "roomId": "14900",       "propId": "6779",       "roomsavail": 1   },   "checkIn": "20190501",   "lastNight": "20190502",   "checkOut": "20190503",   "ownerId": 25748,   "numAdult": 2 } 

Fale sobre problemas de resposta.

  1. No corpo da resposta, as numAdult e numAdult subitamente se tornaram números. E na solicitação foi necessário indicá-los na forma de strings.
  2. A lista de objetos imobiliários é apresentada na forma de propriedades de objetos, cujas chaves são identificadores de sala ( roomId ). Seria lógico esperar que esses dados fossem produzidos como uma matriz. Para nós, isso significa que, para obter uma lista de salas disponíveis, precisamos roomsavail sobre o objeto inteiro, enquanto verificamos a presença de certas propriedades dos objetos roomsavail nele, como roomsavail , e não roomsavail atenção a algo como checkIn e lastNight . Seria necessário verificar o valor da propriedade de roomsavail e, se for maior que 0, poderíamos concluir que a propriedade correspondente está disponível para reserva. Agora vamos dar uma olhada na propriedade roomsavail . Aqui estão as opções para apresentá-lo no corpo da resposta: "roomsavail": "0" e "roomsavail": 1 . Veja o padrão? Se os quartos estiverem ocupados, o valor da propriedade será representado por uma sequência. Se livre - ele se transforma em um número. Isso pode levar a muitos problemas em idiomas estritamente relacionados aos tipos de dados, pois neles a mesma propriedade não deve aceitar valores de tipos diferentes. Em conexão com o exposto acima, eu gostaria de sugerir que os desenvolvedores usem matrizes de objetos JSON para representar determinados conjuntos de dados e não usem construções inconvenientes na forma de pares de valores-chave semelhantes aos que estamos considerando aqui. Além disso, é necessário garantir que os campos de objetos homogêneos não contenham dados de tipos diferentes. Uma resposta do servidor formatada corretamente pode se parecer com a abaixo. Observe que, ao apresentar dados neste formulário, as informações da sala não contêm dados duplicados.

 {   "properties": [       {           "id": 4478,           "rooms": [               {                   "id": 12328,                   "available": false               }           ]       },       {           "id": 5729,           "rooms": [               {                   "id": 13219,                   "available": false               }           ]       },       {           "id": 6779,           "rooms": [               {                   "id": 14900,                   "available": true               }           ]       }   ],   "checkIn": "2019-05-01",   "lastNight": "2019-05-02",   "checkOut": "2019-05-03",   "ownerId": 25748,   "numAdult": 2 } 

Problema 3: tratamento de erros


É assim que a manipulação de erros é organizada na API considerada aqui: o sistema envia respostas com um código de 200 para todas as solicitações, mesmo que ocorra um erro. Isso significa que a única maneira de distinguir uma resposta normal de uma resposta com uma mensagem de erro é analisar o corpo da resposta e verificar a presença de campos de error ou errorCode nela. Apenas os 6 códigos de erro a seguir são fornecidos na API.


Códigos de erro da API Beds24

Sugiro que todos que leem isso tentem não retornar uma resposta com o código 200 (processamento bem-sucedido da solicitação) se algo der errado durante o processamento da solicitação. Você pode executar esta etapa apenas se for fornecida pela estrutura com base na qual você está desenvolvendo a API. O retorno de códigos de resposta adequados permite que os clientes da API saibam com antecedência se precisam analisar o corpo da resposta ou não, e como fazê-lo (ou seja, analisar uma resposta normal do servidor ou um objeto de erro).

No nosso caso, existem duas maneiras de melhorar a API nessa direção: você pode fornecer um código HTTP especial no intervalo de 400 a 499 para cada um dos 6 erros possíveis (é melhor fazer isso) ou retornar, se ocorrer um erro, o código 500, que permitirá pelo menos, o cliente deve saber antes de analisar o corpo da resposta que contém informações sobre o erro.

Problema número 4: "instruções"


Abaixo estão as "instruções" para usar a API na documentação do projeto:

Leia as seguintes instruções ao usar a API.

  1. As chamadas de API devem ser projetadas para que, durante sua execução, seja necessário enviar e receber uma quantidade mínima de dados.
  2. As chamadas de API são realizadas uma de cada vez. Você deve esperar até a próxima chamada para a API antes de fazer a próxima chamada.
  3. Se você precisar fazer várias chamadas para a API, uma pausa de alguns segundos deve ser fornecida entre elas.
  4. As chamadas de API não precisam ser feitas com muita frequência, mantendo o nível de chamadas no nível mínimo necessário para resolver tarefas do cliente.
  5. O uso excessivo da API dentro de um período de 5 minutos levará à suspensão da sua conta sem notificações adicionais.
  6. Reservamo-nos o direito de bloquear o acesso ao sistema a clientes que, em nossa opinião, usam excessivamente a API. Isso é feito a nosso critério e sem aviso prévio.

Embora os pontos 1 e 4 pareçam bastante justificados, não posso concordar com os outros pontos desta instrução. Considere-os.

  1. Número do item 2. Se você estiver desenvolvendo uma API REST, será assumido que será uma API independente do estado. A independência das chamadas de API das chamadas anteriores é um dos motivos pelos quais a tecnologia REST encontrou ampla aplicação em aplicativos em nuvem. Se um determinado módulo do sistema não suportar o estado, ele poderá ser reimplantado facilmente em caso de erro. Os sistemas baseados nesses módulos são dimensionados facilmente quando a carga sobre eles muda. Ao projetar uma API RESTful, você deve garantir que ela seja uma API independente do estado e que aqueles que a usam não precisem se preocupar com algo como executar apenas uma solicitação por vez.
  2. Número do item 3. Este item parece bastante estranho e ambíguo. Não consigo entender o motivo pelo qual este parágrafo da instrução foi escrito, mas sinto que isso nos diz que, no processo de processamento da solicitação, o sistema executa determinadas ações e, se "distraído" por outra solicitação, não enviado a tempo, isso pode interferir em seu trabalho. Além disso, o fato de o autor do manual dizer "alguns segundos" não permite descobrir a duração exata da pausa que deve ser mantida entre solicitações sucessivas.
  3. Itens nº 5 e nº 6. Refere-se ao "uso excessivo da API", mas nenhum critério para "uso excessivo" é fornecido. Talvez seja 10 pedidos por segundo? Ou talvez 1? Além disso, alguns projetos da web podem ter grandes quantidades de tráfego. Se, sem motivos adequados e sem notificação, eles fecharem o acesso à API de que precisam, seus administradores provavelmente se recusarão a usar essas APIs. Se você escrever essas instruções, use uma linguagem clara e coloque-se no lugar dos usuários que precisam trabalhar com seu sistema, guiados por suas instruções.

Problema número 5: documentação


É assim que a documentação da API se parece.


Documentação da API Beds24

O único problema com esta documentação é sua aparência. Ficaria muito melhor se estivesse bem formatado. Especialmente para mostrar a possível aparência dessa documentação, eu, usando Dillinger , e gastando menos de dois minutos nela, fiz a seguinte versão. Na minha opinião, parece muito melhor do que o acima.


Documentação aprimorada

Para criar esses materiais, é recomendável usar ferramentas especiais. Se estamos falando de documentos simples semelhantes ao descrito acima, algo como um arquivo de remarcação regular é suficiente para o design deles. Se a documentação for mais complicada, então, para seu design, é melhor usar ferramentas como Swagger ou Apiary .

A propósito, se você quiser dar uma olhada na documentação da API do Beds24, dê uma olhada aqui .

Problema 6: segurança


A documentação para todos os pontos de extremidade da API diz o seguinte:

Para usar essas funções, o acesso à API deve ser permitido. Isso é feito no menu CONFIGURAÇÕES → CONTA → ACESSO À CONTA.

No entanto, na realidade, qualquer pessoa pode acessar essa API e, usando algumas chamadas, obter informações sem fornecer credenciais. Por exemplo, isso também se aplica a consultas relacionadas à disponibilidade de determinadas habitações. Isso é discutido em outra parte da documentação.

A maioria dos métodos JSON requer uma chave de API para acessar uma conta. A chave de acesso da API pode ser definida usando o menu CONFIGURAÇÕES → CONTA → ACESSO À CONTA.

Além da explicação incompreensível dos problemas de autenticação, o usuário deve criar a chave para acessar a API por conta própria (isso é feito, a propósito, preenchendo manualmente o campo correspondente, alguns meios para criar chaves automaticamente não são fornecidos). O comprimento da chave deve ter entre 16 e 64 caracteres. Se você permitir que os usuários criem suas próprias chaves para acessar a API, isso pode levar ao aparecimento de chaves muito inseguras que podem ser facilmente capturadas. Em uma situação semelhante, são possíveis problemas associados ao conteúdo das chaves, pois você pode inserir qualquer coisa no campo chave. Na pior das hipóteses, isso pode levar a um ataque ao serviço usando injeção de SQL ou algo assim. Ao projetar uma API, não permita que os usuários criem chaves para acessar a API. Em vez disso, gere chaves para elas automaticamente. O usuário não deve poder alterar o conteúdo dessa chave, mas, se necessário, deve poder gerar uma nova chave, reconhecendo a antiga como inválida.

No caso de solicitações que requerem autenticação, vemos outro problema. Consiste no fato de que um token de autenticação deve ser enviado como parte do corpo da solicitação. Aqui está como é descrito na documentação.


Exemplo de autenticação da API Beds24

Se o token de autenticação for transmitido no corpo da solicitação, isso significa que o servidor precisará analisar o corpo da solicitação antes de alcançar a chave. Depois disso, ele extrai a chave, executa a autenticação e decide - o que deve fazer com a solicitação - cumpri-la ou não. Se a autenticação for bem-sucedida, o servidor não estará sujeito a carga adicional, pois nesse caso o corpo da solicitação ainda precisará ser analisado. Mas, se a solicitação falhar na autenticação, um tempo valioso do processador será gasto na análise do corpo da solicitação por nada. Seria melhor enviar um token de autenticação no cabeçalho da solicitação, usando algo como um esquema de autenticação do Portador . Com essa abordagem, o servidor precisará analisar o corpo da solicitação apenas se a autenticação for bem-sucedida. Outro motivo pelo qual recomendamos o uso de um esquema padrão como o Bearer para autenticação é o fato de a maioria dos desenvolvedores estar familiarizada com esses esquemas.

Problema número 7: desempenho


Esta é a última edição da minha lista, mas não diminui sua importância. O fato é que leva um pouco mais de um segundo para concluir a solicitação para a API em questão. Em aplicações modernas, esses atrasos podem ser inaceitáveis. De fato, aqui você pode aconselhar todos os envolvidos no desenvolvimento da API a não esquecer o desempenho.

Sumário


Apesar de todos os problemas que estávamos falando aqui, a API em questão nos permitiu resolver os problemas enfrentados pelo projeto. Mas os desenvolvedores levaram bastante tempo para entender a API e implementar tudo o que precisavam. Além disso, para resolver problemas simples, eles precisavam escrever um código bastante complicado. Se essa API fosse projetada como deveria, o trabalho seria feito mais rapidamente e a solução pronta para uso seria mais simples.

Portanto, gostaria de pedir a todos que projetam a API que pensem em como os usuários de seus serviços trabalharão com ela. Verifique se a documentação da API descreve completamente seus recursos, para que seja compreensível e bem projetado. Controle a nomeação de entidades, verifique se os dados que sua API emite ou recebe são claramente estruturados para que seja fácil e conveniente trabalhar com eles. Além disso, não se esqueça da segurança e do tratamento correto dos erros. Se você levar em conta tudo o que falamos ao criar a API, não precisará escrever algo como as estranhas "instruções" que discutimos acima para trabalhar com ela.

Como já mencionado, este material não se destina a desencorajar os leitores a usar o Beds24 ou qualquer outro sistema com uma API mal projetada. Meu objetivo era mostrar exemplos de erros e abordagens para sua solução, fazer recomendações, seguindo as quais todos pudessem melhorar a qualidade de seus desenvolvimentos. Espero que este material atraia a atenção dos programadores que o leem para a qualidade das soluções que desenvolvem. Isso significa que haverá mais boas APIs no mundo.

Caros leitores! Você encontrou APIs mal projetadas?

Source: https://habr.com/ru/post/pt436888/


All Articles