Criando parte do CRUD de uma API HTTP

5

Esta questão está no contexto de aplicações baseadas na web. Um servidor web expondo uma API HTTP para clientes (por exemplo, executando em um navegador, mas não necessariamente). Normalmente, o servidor web seria conectado a alguma espécie de datastorage.

Minha preocupação é a API exposta pelo servidor HTTP e como projetá-lo.

Eu acho que, muitas vezes, uma grande parte da API exposta ao cliente é apenas uma Interface CRUD para o armazenamento de dados. Por exemplo, a API quase sempre contém coisas como:

- get_all_users()
- get_this_user(id)
- create_new_user(name, email, phone, password)
- update_user(id, name?, email?, phone?, password?)
- delete_user(id)

Isso não é tudo que a API faz, mas é algo comum a muitos de APIs com as quais trabalhei.

Dependendo do que o cliente precisa, muitas vezes acabo com mais e mais coisas na API como estas:

- get_user_by_email(email)
- get_all_users_along_with_number_of_lincenses_for_each()
- delete_all_these_users_at_once(array [])
- get_users_whose_profile_is(profile_name)
- get_names_of_users_who_are_admin_profiles()
- .... etc

Em outras palavras, o cliente geralmente precisa fazer junções, filtrar e classificar dados de todas as formas.

O que me levou a APIs excessivamente inchadas (assim como a acima) suportando todos os tipos de opções para filtragem, classificação e junção com dados de outras tabelas.

A API resultante é muito complexa para que o cliente use e aprenda e para o servidor manter e testar.

(IMO a API HTTP do Zabbix sofre desta síndrome).

Perguntas:

  • Você tem experiência semelhante?

  • Você tem algumas dicas sobre como encontrar um bom equilíbrio entre Completude e complexidade da API.

PS. Estou ciente do REST, mas não acho que resolve o problema, a API fornecida ainda é muito básica, e não resolve o problema get_users_along_with_number_of_licenses () ou o get_names_of_users_which_are_admin () chamadas api.;

    
por cerendata 07.08.2015 / 12:13
fonte

3 respostas

5

Esse é um dilema clássico de design de API, independentemente de ser fornecido por um serviço da Web, de vincular uma biblioteca ou de fazer parte de uma base de código que é construída junto com todo o restante. Tudo se resume a saber se você prefere muitas funções, cada uma fazendo uma coisa específica, ou se você tem menos funções mais parametrizadas.

Como as chamadas da web são mais "caras" em termos de tempo, muitos projetistas de API da web optam por chamadas menos ricas e mais sofisticadas. Uma interface CRUD sobre HTTP forneceria as operações Ler (GET), Gravar / Criar (PUT / POST) e Excluir (DELETE), com todos os parâmetros necessários sendo fornecidos no corpo da solicitação ou codificados como parâmetros na URL (QueryString).

Para abordar sua crescente lista de maneiras de filtrar e organizar suas operações de Leitura, você deve definir um conjunto mais rico de entradas, que podem ser tão complexas e estruturadas quanto desejar, já que o HTTP entregará qualquer coisa que você solicitar. A maioria das pessoas projeta seus parâmetros como pares nome / valor, ou como objetos Json, ou como documentos xml. Seus parâmetros podem incluir critérios de filtragem e opções indicando quais dados você deseja que a chamada retorne.

Atlassian faz isso com suas APIs REST para JIRA e Confluence (e outras). Eles fornecem URLs (chamadas) para obter todos os problemas, um problema ou alguns problemas com base em um filtro ou consulta. Cada chamada GET suporta um parâmetro (neste caso, chamado expand , que permite ao chamador solicitar que determinados dados de retorno opcionais sejam incluídos na resposta.

Quanto à classificação, se o serviço estiver retornando muitos dados ou dados fragmentados (paginados), é interessante projetar a chamada com um parâmetro de classificação para facilitar a vida do chamador. Para listas menores de dados, pode ser mais fácil apenas fornecer os dados em uma ordem e deixar o cliente classificá-los da maneira que quiserem.

O REST é atualmente popular porque é altamente desacoplado entre o chamador e o provedor, o que oferece extrema flexibilidade. Você pode preferir algo como XMLRPC, que se parece mais com as chamadas de função para o código do cliente. Você ainda pode fornecer uma chamada que aceite muitos parâmetros ou um grande parâmetro semelhante a um objeto. Ainda é com você.

Faça um favor a si mesmo e aos seus usuários e certifique-se de documentar sua API completamente e torná-la prontamente disponível. A tendência nos serviços da web ultimamente é se afastar de ligações estreitas (como em SOAP e XMLRPC), o que torna mais difícil para compiladores e IDEs ajudar você a saber como fazer uma chamada. Documentação completa e acessível será sua amiga.

    
por 07.08.2015 / 14:04
fonte
1

Eu fiz coisas como o REST, que, como você diz, não resolve todos os problemas, embora pelo menos alguns deles possivelmente.

Basicamente, os recursos de pesquisa que implementei usam params de URL, em vez de ter um único URL para cada um deles. Então, seu get_user_by_email seria apenas

/[email protected]

Similar, você pode manipular os dados retornados para itens como get_all_users_along_with_number_of_lincenses_for_each

/users?fields=number_licenses

Ou até mesmo retornar dados aninhados, como

/users?includes=licenses

Pode até incluir vários:

/users?includes=licenses,orders

que retornaria um objeto JSON aninhado com usuários e todos os registros de licença.

ou para o seu get_names_of_users_who_are_admin_profiles assim:

/users?admin=true&fields=id,name

O método get_users_whose_profile_is seria uma rota aninhada simples:

/profiles/{profile_id}/users

É claro que isso não resolve totalmente o problema de complexidade, mas pelo menos reduz a quantidade de pontos de acesso que o usuário tem para acessar e aprender. Ainda assim, ele precisa saber quais campos existem e como consultá-los (temos até alguns recursos de pesquisa de texto completo para alguns recursos).

Outro problema aqui pode ser as permissões, já que alguns usuários podem ter permissão para pesquisar e solicitar diferentes tipos de dados. Especialmente quais campos ele pode solicitar podem ser problemáticos. Ainda pelo menos com os endpoints reduzidos, o primeiro nível de permissões é mais fácil (o usuário pode solicitar / users / ou ele não pode.

Como isso é fácil de implementar pode depender da sua pilha de tecnologia. Usamos o Ruby on Rails que permite escrever alguns métodos genéricos que facilitam o processamento de coisas como fields=name,id,street,city ou includes=orders,licenses (embora tenha cuidado, isso novamente precisa garantir que as permissões sejam tratadas corretamente)

    
por 07.08.2015 / 13:00
fonte
1

Sugiro que você 1) saiba o que outras APIs REST fazem e 2) considere OData

Eu adoro Odata porque ele suporta todos os cenários listados em uma única chamada de API. Eu sou tendencioso, porque em C # você pode implementar um servidor Odata em apenas algumas linhas de código se você usar o Entity Framework. Há também bibliotecas do lado do cliente que podem formatar URLs de OData.

Deixe-me aplicar alguns dos seus exemplos de consulta ao OData:

  • get_user_by_email (email)

link $ filter = e-mail eq '[email protected]'

  • get_all_users_along_with_number_of_lincenses_for_each ()

link $ filter = email eq '[email protected]' / LICENSES

  • get_names_of_users_who_are_admin_profiles ()

link $ filter = admin eq true $ select = Nome

No geral, o OData basicamente mapeia tudo o que você encontraria em uma cláusula SQL select na string Query. A única queixa que eu tenho sobre isso é que é um pouco muito poderoso, então você precisa fazer algum trabalho no lado do servidor para se certificar de que alguém não faça uma consulta complicada para um milhão de registros. Limite o número de resultados, limite o tempo que a consulta pode ser executada, etc.

Com o seu exemplo de exclusão, há algumas maneiras comuns de transmitir matrizes. Se você ler a documentação em Swagger , você pode obter uma ideia das diferentes maneiras pelas quais as pessoas tendem a passar matrizes como argumentos. O Swagger lista os valores separados por vírgula, separados por espaços, separados por tabulação, separados por pipe e repetindo o parâmetro várias vezes. CSV é o padrão, então isso me diz que as pessoas tendem a usá-lo muito. O ASP.NET MVC mapeia nativamente o último exemplo para um array. Então, considere:

  • delete_all_these_users_at_once (array [])

HTTP DELETE link ou HTTP DELETE link

    
por 07.08.2015 / 18:12
fonte