Prática recomendada da API REST: como aceitar a lista de valores de parâmetros como entrada [fechada]

410

Estamos lançando uma nova API REST e eu queria algumas sugestões da comunidade sobre práticas recomendadas sobre como deveríamos ter os parâmetros de entrada formatados:

No momento, nossa API é muito centrada em JSON (retorna apenas JSON). O debate sobre se queremos / precisamos retornar XML é uma questão separada.

Como nossa saída de API é centrada em JSON, seguimos um caminho em que nossas entradas são um pouco centradas em JSON e eu estive pensando que pode ser conveniente para alguns, mas estranho em geral.

Por exemplo, para obter alguns detalhes do produto em que vários produtos podem ser extraídos de uma só vez, atualmente temos:

http://our.api.com/Product?id=["101404","7267261"]

Devemos simplificar isso como:

http://our.api.com/Product?id=101404,7267261

Ou a entrada JSON é útil? Mais dor?

Podemos aceitar os dois estilos, mas essa flexibilidade realmente causa mais confusão e dores de cabeça (manutenção, documentação etc.)?

Um caso mais complexo é quando queremos oferecer contribuições mais complexas. Por exemplo, se queremos permitir vários filtros na pesquisa:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

Não queremos necessariamente colocar os tipos de filtro (por exemplo, productType e color) como nomes de solicitação como este:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Porque queríamos agrupar todas as entradas de filtro.

No final, isso realmente importa? Pode ser que haja tantos utilitários JSON por aí que o tipo de entrada simplesmente não importe muito.

Sei que nossos clientes JavaScript que fazem chamadas AJAX para a API podem apreciar as entradas JSON para facilitar sua vida.

whatupwilly
fonte

Respostas:

341

Um passo atrás

Em primeiro lugar, o REST descreve um URI como um ID universalmente exclusivo. Muitas pessoas ficam presas à estrutura dos URIs e quais são mais "tranquilos" do que outros. Esse argumento é tão ridículo quanto dizer que nomear alguém "Bob" é melhor do que nomear "Joe" - ambos os nomes fazem o trabalho de "identificar uma pessoa". Um URI nada mais é do que um nome universalmente exclusivo .

Portanto, aos olhos do REST, discutimos se ?id=["101404","7267261"]é mais repousante do que ?id=101404,7267261ou \Product\101404,7267261é algo fútil.

Agora, dito isso, muitas vezes como os URIs são construídos geralmente podem servir como um bom indicador para outros problemas em um serviço RESTful. Existem algumas bandeiras vermelhas nos seus URIs e perguntas em geral.

Sugestões

  1. Vários URIs para o mesmo recurso e Content-Location

    Podemos aceitar os dois estilos, mas essa flexibilidade realmente causa mais confusão e dores de cabeça (manutenção, documentação etc.)?

    URIs identificam recursos. Cada recurso deve ter um URI canônico. Isso não significa que você não pode ter dois URIs apontados para o mesmo recurso, mas existem maneiras bem definidas de fazê-lo. Se você decidir usar o JSON e os formatos baseados em lista (ou qualquer outro formato), precisará decidir qual desses formatos é o principal URI canônico . Todas as respostas a outros URIs que apontam para o mesmo "recurso" devem incluir o Content-Locationcabeçalho .

    Seguindo a analogia do nome, ter vários URIs é como ter apelidos para as pessoas. É perfeitamente aceitável e muitas vezes bastante útil, no entanto, se eu estiver usando um apelido, provavelmente ainda quero saber o nome completo - a maneira "oficial" de me referir a essa pessoa. Dessa maneira, quando alguém menciona alguém pelo nome completo, "Nichloas Telsa", sei que está falando da mesma pessoa a quem me refiro como "Nick".

  2. "Pesquisar" no seu URI

    Um caso mais complexo é quando queremos oferecer contribuições mais complexas. Por exemplo, se queremos permitir vários filtros na pesquisa ...

    Uma regra geral minha é que, se o seu URI contiver um verbo, pode ser uma indicação de que algo está errado. Os URIs identificam um recurso, no entanto, não devem indicar o que estamos fazendo com esse recurso. Esse é o trabalho do HTTP ou, em termos tranqüilos, nossa "interface uniforme".

    Para vencer a analogia do nome, usar um verbo em um URI é como mudar o nome de alguém quando você deseja interagir com ele. Se estou interagindo com Bob, o nome de Bob não se torna "BobHi" quando quero dizer oi. Da mesma forma, quando queremos "pesquisar" produtos, nossa estrutura de URI não deve mudar de "/ Product / ..." para "/ Search / ...".

Respondendo à sua pergunta inicial

  1. Em relação a ["101404","7267261"]vs 101404,7267261: minha sugestão aqui é evitar a sintaxe JSON por uma questão de simplicidade (por exemplo, não exija que seus usuários façam codificação de URL quando você realmente não precisar). Isso tornará sua API um pouco mais utilizável. Melhor ainda, como outros recomendaram, use o application/x-www-form-urlencodedformato padrão , pois provavelmente será mais familiar para os usuários finais (por exemplo ?id[]=101404&id[]=7267261). Pode não ser "bonito", mas URIs bonitos não significa URIs utilizáveis. No entanto, para reiterar meu ponto inicial, em última análise, quando se fala em REST, isso não importa. Não pense muito sobre isso.

  2. Seu exemplo complexo de URI de pesquisa pode ser resolvido da mesma maneira que o exemplo do seu produto. Eu recomendaria o application/x-www-form-urlencodedformato novamente, pois já é um padrão com o qual muitos estão familiarizados. Além disso, eu recomendaria mesclar os dois.

Seu URI ...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Seu URI após ser codificado por URI ...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Pode ser transformado em ...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Além de evitar o requisito de codificação de URL e tornar as coisas um pouco mais padrão, agora homogeneiza a API um pouco. O usuário sabe que, se quiser recuperar um Produto ou Lista de Produtos (ambos são considerados um único "recurso" em termos RESTful), eles estão interessados ​​em /Product/...URIs.

nategood
fonte
67
Queria acompanhar e observar que a []sintaxe nem sempre é suportada (e, apesar de comum, pode até violar a especificação do URI). Alguns servidores HTTP e linguagens de programação preferem apenas repetir o nome (por exemplo productType=value1&productType=value2).
Nategood 26/08/13
1
A pergunta inicial com esta consulta .. "/ Search? Term = pumas & Filters = {" productType ": [" Vestuário "," Bolsas "]," cor ": [" Preto "," Vermelho "]}" se traduz em .. . (productType == vestuário || productType == bolsas) && (color == preto || cor == vermelho) MAS SUA SOLUÇÃO: / Produto? term = pumas & productType [] = Vestuário e productType [] = Bolsas e cor [] = Preto e cor [] = Vermelho parece ser traduzido para ... (productType == vestuário || productType == bolsas || cor == preto || cor == vermelho) ou Ou (productType == vestuário && productType == bolsas && color == preto && color == vermelho) O que me parece um pouco diferente. Ou eu entendi mal?
Thomas Cheng
2
E as entradas na solicitação de postagem? Eu queria saber se estamos atualizando um recurso, então é uma prática recomendada enviar a consulta / filtro e dados no corpo em um formato padrão. por exemplo. se eu quiser alterar os dados relacionados com o usuário usando a API /user/e no corpo, eu vou enviar { q:{}, d: {} }com qa consulta com o usuário será consultado no banco de dados e dcomo os dados modificados.
molécula de
1
O que você faz quando a lista pode ser muito grande? O URI é limitado em comprimento, dependendo do navegador. Normalmente, mudei para uma solicitação de postagem e enviei a lista no corpo. Alguma sugestão aí?
Troy Cosentino
4
Teria que ser MUITO grande (consulte stackoverflow.com/questions/417142/… ), mas sim, nos casos mais extremos, talvez você precise usar o corpo da solicitação. POSTAR consultas para recuperação de dados é uma daquelas coisas que os RESTafarians adoram debater.
nategood
234

A maneira padrão de passar uma lista de valores como parâmetros de URL é repeti-los:

http://our.api.com/Product?id=101404&id=7267261

A maioria dos códigos de servidor interpretará isso como uma lista de valores, embora muitos tenham simplificações de valor único, portanto você pode precisar procurar.

Valores delimitados também estão bem.

Se você precisar enviar JSON para o servidor, não gosto de vê-lo na URL (que é um formato diferente). Em particular, os URLs têm uma limitação de tamanho (na prática, se não na teoria).

A maneira como eu vi alguns fazerem uma consulta complicada RESTfully é em duas etapas:

  1. POST seus requisitos de consulta, recebendo de volta um ID (essencialmente criando um recurso de critério de pesquisa)
  2. GET a pesquisa, referenciando o ID acima
  3. opcionalmente, DELETE os requisitos de consulta, se necessário, mas observe que eles estão disponíveis para reutilização.
Kathy Van Stone
fonte
8
Obrigado Kathy. Acho que estou com você e realmente não gosto de ver o JSON na URL também. No entanto, não sou fã de publicar uma pesquisa que é uma operação GET inerente. Você pode apontar para um exemplo disso?
whatupwilly
1
Se as consultas puderem funcionar como parâmetros simples, faça isso. A fonte foi a partir da lista de discussão resto-discutir: tech.groups.yahoo.com/group/rest-discuss/message/11578
Kathy Van Pedra
2
Se você só quer mostrar dois recursos, a resposta de James Westgate é mais típico
Kathy Van Pedra
Essa é a resposta correta. Num futuro próximo, tenho certeza de que veremos algum filtro = id em (a, b, c, ...) suportado pelo OData ou algo parecido.
Bart Calixto
É assim que o Akka HTTP funciona
Joan
20

Primeiro:

Eu acho que você pode fazer isso de duas maneiras

http://our.api.com/Product/<id> : se você quer apenas um registro

http://our.api.com/Product : se você quiser todos os registros

http://our.api.com/Product/<id1>,<id2> : como James sugeriu, pode ser uma opção, pois o que vem depois da tag Product é um parâmetro

Ou o que eu mais gosto é:

Você pode usar a propriedade Hipermídia como o mecanismo do estado do aplicativo (HATEOAS) de um RestFul WS e fazer uma chamada http://our.api.com/Productque deve retornar os URLs equivalentes http://our.api.com/Product/<id>e chamá-los depois disso.

Segundo

Quando você precisar fazer consultas nas chamadas de URL. Eu sugeriria o uso do HATEOAS novamente.

1) Faça uma chamada para http://our.api.com/term/pumas/productType/clothing/color/black

2) Faça uma chamada para http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Usando HATEOAS) Faça uma chamada para ` http://our.api.com/term/pumas/productType/ -> receba os URLs com todas as roupas possíveis -> ligue para os que você deseja (roupas e bolsas) - > receba os possíveis URLs de cores -> ligue para os que você deseja

Diego Dias
fonte
1
Fui colocado em uma situação semelhante há alguns dias, Tendo que ajustar uma API de descanso (HATEOAS) para obter uma lista (grande) filtrada de objetos e escolhi sua segunda solução. Não é demais recordar a API repetidamente para cada uma delas?
Samson
Realmente depende do seu sistema .... Se for simples e com poucas "opções", provavelmente deve ser um exagero. No entanto, se você tiver algumas listas realmente grandes, pode se tornar realmente problemático fazer tudo em uma grande chamada, além disso, se sua API for pública, pode se tornar complicada para os usuários (se for privada, será mais fácil ... apenas ensine os usuários que você conhece). Como alternativa, você poderia implementar tanto estilo, o HATEOAS e uma chamada "matriz de não-reparador" para usuários avançados
Diego Dias
Estou construindo um serviço web api repousante em trilhos e preciso seguir a mesma estrutura de URL que acima ( our.api.com/term/pumas/productType/clothing/color/black ). Mas não sei como configurar as rotas adequadamente.
perfil completo de Rubyist
são term, productType e color seus controladores? Se assim for, você só precisa fazer: recursos: Termo fazer recursos: ProductType fazer recursos: end end cor
Diego Dias
productType e color são os parâmetros. Portanto, os parâmetros de productType são roupas e os parâmetros de roupas são pretos
rubyist
12

Você pode querer conferir o RFC 6570 . Esta especificação de modelo de URI mostra muitos exemplos de como os URLs podem conter parâmetros.

Darrel Miller
fonte
1
A seção 3.2.8 parece ser o que é aplicável. Embora seja interessante notar que esse é apenas um padrão proposto e parece não ter ido além desse ponto.
Mike Post
3
@MikePost Agora que a IETF passou para um processo de maturidade em duas etapas para documentos de "rastreamento de padrões", espero que o 6570 permaneça assim por mais alguns anos antes de passar para um "Padrão da Internet". tools.ietf.org/html/rfc6410 A especificação é extremamente estável, possui muitas implementações e é amplamente utilizada.
Darrel Miller
Ah, eu não estava ciente dessa mudança. (Ou, TIL IETF agora é mais razoável.) Obrigado!
Mike Post
8

Primeiro caso:

Uma pesquisa normal de produto seria assim

http://our.api.com/product/1

Então, eu estou pensando que a melhor prática seria para você fazer isso

http://our.api.com/Product/101404,7267261

Segundo caso

Pesquise com parâmetros de string de consulta - bem assim. Eu ficaria tentado a combinar termos com AND e OR em vez de usar [].

PS Isso pode ser subjetivo, faça o que você se sentir confortável.

O motivo para colocar os dados no URL é para que o link possa ser colado em um site / compartilhado entre usuários. Se isso não for um problema, use um JSON / POST.

EDIT: Na reflexão, acho que essa abordagem combina com uma entidade com uma chave composta, mas não com uma consulta para várias entidades.

James Westgate
fonte
3
Obviamente, em ambos os exemplos, a trilha /não deve estar lá, pois o URI endereça um recurso, não uma coleção.
Lawrence Dol
2
Eu sempre pensei que os verbos HTTP, em um uso REST, eram para executar ações específicas, e esta era a linha de orientação: GET: recuperar / ler objeto, POST criar objeto, PUT atualizar objeto existente e DELETE excluir um objeto. Então, eu não usaria um POST para recuperar algo. Se eu quiser uma lista de objeto em particular (filtro), eu faria um GET com uma lista de parâmetros de URL (separados por uma vírgula parece bom)
Alex
1

Apoiarei a resposta da nategood, pois ela está completa e parecia ter agradado às suas necessidades. No entanto, gostaria de adicionar um comentário sobre a identificação de vários (1 ou mais) recursos dessa maneira:

http://our.api.com/Product/101404,7267261

Ao fazer isso, você:

Complexifique os clientes , forçando-os a interpretar sua resposta como uma matriz, o que para mim é contra-intuitivo se eu fizer a seguinte solicitação:http://our.api.com/Product/101404

Crie APIs redundantes com uma API para obter todos os produtos e a acima para obter 1 ou mais. Como você não deve mostrar mais de uma página de detalhes para um usuário em benefício do UX, acredito que ter mais de um ID seria inútil e puramente usado para filtrar os produtos.

Pode não ser tão problemático, mas você precisará lidar com isso do lado do servidor retornando uma única entidade (verificando se sua resposta contém uma ou mais) ou permitir que os clientes gerenciem.

Exemplo

Quero encomendar um livro da Amazing . Sei exatamente qual é o livro e o vejo na lista ao navegar pelos livros de Terror:

  1. 10 000 linhas incríveis, 0 teste incrível
  2. O retorno do monstro incrível
  3. Vamos duplicar código incrível
  4. O incrível começo do fim

Depois de selecionar o segundo livro, sou redirecionado para uma página que detalha a parte do livro de uma lista:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

Ou em uma página que me fornece apenas os detalhes completos desse livro?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

Minha opinião

Eu sugeriria o uso da identificação na variável path quando a unicidade é garantida ao obter os detalhes desse recurso. Por exemplo, as APIs abaixo sugerem várias maneiras de obter os detalhes de um recurso específico (supondo que um produto tenha um ID exclusivo e uma especificação para esse produto tenha um nome exclusivo e você possa navegar de cima para baixo):

/products/{id}
/products/{id}/specs/{name}

No momento em que você precisar de mais de um recurso, sugiro filtrar a partir de uma coleção maior. Para o mesmo exemplo:

/products?ids=

Obviamente, esta é a minha opinião, pois não é imposta.

gumol
fonte