API REST Meilleure pratique: comment accepter la liste des valeurs des paramètres en entrée [fermé]

410

Nous lançons une nouvelle API REST et je voulais des commentaires de la communauté sur les meilleures pratiques concernant la façon dont nous devrions formater les paramètres d'entrée:

À l'heure actuelle, notre API est très centrée sur JSON (ne renvoie que JSON). Le débat de savoir si nous voulons / devons renvoyer XML est une question distincte.

Comme notre sortie API est centrée sur JSON, nous avons emprunté un chemin où nos entrées sont un peu centrées sur JSON et j'ai pensé que cela pourrait être pratique pour certains mais bizarre en général.

Par exemple, pour obtenir quelques détails sur les produits où plusieurs produits peuvent être extraits en même temps, nous avons actuellement:

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

Faut-il simplifier cela comme:

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

Ou bien avoir une entrée JSON à portée de main? Plus de douleur?

Nous pouvons vouloir accepter les deux styles, mais cette flexibilité provoque-t-elle réellement plus de confusion et de maux de tête (maintenabilité, documentation, etc.)?

Un cas plus complexe est celui où nous voulons offrir des entrées plus complexes. Par exemple, si nous voulons autoriser plusieurs filtres sur la recherche:

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

Nous ne voulons pas nécessairement mettre les types de filtres (par exemple productType et color) en tant que noms de requête comme ceci:

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

Parce que nous voulions regrouper toutes les entrées de filtre.

En fin de compte, est-ce vraiment important? Il est probable qu'il y ait tellement d'utilitaires JSON que le type d'entrée n'a pas beaucoup d'importance.

Je sais que nos clients JavaScript effectuant des appels AJAX à l'API peuvent apprécier les entrées JSON pour leur faciliter la vie.

quoi
la source

Réponses:

341

Un pas en arrière

D'abord et avant tout, REST décrit un URI comme un ID universellement unique. Beaucoup trop de gens sont pris dans la structure des URI et quels URI sont plus "reposants" que les autres. Cet argument est aussi ridicule que de dire que nommer quelqu'un "Bob" est mieux que de le nommer "Joe" - les deux noms font "identifier une personne". Un URI n'est rien de plus qu'un nom universellement unique .

Ainsi , dans les yeux de REST se disputaient au sujet ?id=["101404","7267261"]est plus reposant que ?id=101404,7267261ou \Product\101404,7267261est un peu futile.

Maintenant, cela dit, plusieurs fois comment les URI sont construits peuvent généralement servir de bon indicateur pour d'autres problèmes dans un service RESTful. Il y a quelques drapeaux rouges dans vos URI et votre question en général.

Suggestions

  1. URI multiples pour la même ressource et Content-Location

    Nous pouvons vouloir accepter les deux styles, mais cette flexibilité provoque-t-elle réellement plus de confusion et de maux de tête (maintenabilité, documentation, etc.)?

    Les URI identifient les ressources. Chaque ressource doit avoir un URI canonique. Cela ne signifie pas que vous ne pouvez pas avoir deux URI pointant vers la même ressource, mais il existe des façons bien définies de le faire. Si vous décidez d'utiliser à la fois le JSON et les formats basés sur une liste (ou tout autre format), vous devez décider lequel de ces formats est l' URI canonique principal . Toutes les réponses à d'autres URI qui pointent vers la même "ressource" doivent inclure l'en- Content-Locationtête .

    S'en tenir à l'analogie du nom, avoir plusieurs URI, c'est comme avoir des surnoms pour les gens. C'est parfaitement acceptable et souvent très pratique, mais si j'utilise un surnom, je veux probablement connaître son nom complet - la façon "officielle" de se référer à cette personne. De cette façon, lorsque quelqu'un mentionne quelqu'un par son nom complet, "Nichloas Telsa", je sais qu'il s'agit de la même personne que j'appelle "Nick".

  2. "Rechercher" dans votre URI

    Un cas plus complexe est celui où nous voulons offrir des entrées plus complexes. Par exemple, si nous voulons autoriser plusieurs filtres sur la recherche ...

    Une règle générale est la suivante: si votre URI contient un verbe, cela peut indiquer que quelque chose ne va pas. Les URI identifient une ressource, mais ils ne doivent pas indiquer ce que nous faisons à cette ressource. C'est le travail de HTTP ou en termes reposants, notre "interface uniforme".

    Pour battre l'analogie avec le nom, utiliser un verbe dans un URI, c'est comme changer le nom de quelqu'un quand on veut interagir avec lui. Si j'interagis avec Bob, le nom de Bob ne devient pas "BobHi" quand je veux lui dire bonjour. De même, lorsque nous voulons "rechercher" des produits, notre structure d'URI ne doit pas passer de "/ Product / ..." à "/ Search / ...".

Répondre à votre question initiale

  1. Concernant ["101404","7267261"]vs 101404,7267261: Ma suggestion ici est d'éviter la syntaxe JSON pour des raisons de simplicité (c'est-à-dire ne pas exiger que vos utilisateurs fassent un encodage URL quand vous n'y êtes pas vraiment obligé). Cela rendra votre API un peu plus utilisable. Mieux encore, comme d'autres l'ont recommandé, optez pour le application/x-www-form-urlencodedformat standard car il sera probablement plus familier à vos utilisateurs finaux (par exemple ?id[]=101404&id[]=7267261). Ce n'est peut-être pas "joli", mais les jolis URI ne signifient pas nécessairement les URI utilisables. Cependant, pour réitérer mon point initial, finalement, quand on parle de REST, cela n'a pas d'importance. Ne vous attardez pas trop dessus.

  2. Votre exemple d'URI de recherche complexe peut être résolu de la même manière que votre exemple de produit. Je recommanderais de recommencer le application/x-www-form-urlencodedformat car c'est déjà une norme que beaucoup connaissent. De plus, je recommanderais de fusionner les deux.

Votre URI ...

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

Votre URI après avoir été encodé en 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

Peut être transformé en ...

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

En plus d'éviter l'exigence d'encodage d'URL et de rendre les choses un peu plus standard, il homogénéise maintenant un peu l'API. L'utilisateur sait que s'il souhaite récupérer un produit ou une liste de produits (les deux sont considérés comme une seule "ressource" en termes RESTful), il est intéressé par les /Product/...URI.

nategood
la source
67
Je voulais faire un suivi et noter que la []syntaxe n'est pas toujours prise en charge (et bien qu'elle soit courante, elle peut même violer la spécification URI). Certains serveurs HTTP et langages de programmation préféreront simplement répéter le nom (par exemple productType=value1&productType=value2).
nategood
1
La question initiale avec cette requête .. "/ Recherche? Term = pumas & filters = {" productType ": [" Vêtements "," Sacs "]," couleur ": [" Noir "," Rouge "]}" se traduit par .. . (productType == vêtements || productType == sacs) && (couleur == noir || color == rouge) MAIS VOTRE SOLUTION: / Product? term = pumas & productType [] = Clothing & productType [] = Sacs & color [] = Black & color [] = Le rouge semble se traduire par ... Soit (productType == vêtements || productType == sacs || color == noir || color == rouge) ou Soit (productType == vêtements && productType == sacs && color == noir && couleur == rouge) Ce qui me semble un peu différent. Ou ai-je mal compris?
Thomas Cheng
2
Qu'en est-il des entrées dans la demande de publication? Je voulais savoir si nous mettons à jour une ressource, alors c'est une mauvaise pratique d'envoyer la requête / filtre et les données dans le corps dans un format standard. par exemple. si je veux changer les données liées à l'utilisateur en utilisant l'api /user/et dans le corps, je vais envoyer { q:{}, d: {} }avec qcomme requête par avec l'utilisateur sera interrogé dans la base de données et en dtant que données modifiées.
molécule
1
Que faites-vous lorsque la liste peut être très longue? L'URI est limité en longueur selon le navigateur. J'ai généralement basculé vers une demande de publication et envoyé la liste dans le corps. Y a-t-il des suggestions?
Troy Cosentino
4
Il devrait être TRÈS volumineux (voir stackoverflow.com/questions/417142/… ), mais oui, dans les cas les plus extrêmes, vous devrez peut-être utiliser le corps de la demande. POSTER des requêtes pour la récupération de données est l'une des choses que les RESTafariens aiment débattre.
nategood
234

La manière standard de passer une liste de valeurs en tant que paramètres d'URL est de les répéter:

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

La plupart des codes de serveur interpréteront cela comme une liste de valeurs, bien que beaucoup aient des simplifications à valeur unique, vous devrez donc aller chercher.

Les valeurs délimitées sont également correctes.

Si vous devez envoyer du JSON au serveur, je n'aime pas le voir dans l'URL (qui est d'un format différent). En particulier, les URL ont une limitation de taille (en pratique sinon en théorie).

La façon dont j'ai vu certains effectuer une requête compliquée RESTfully se déroule en deux étapes:

  1. POST vos exigences de requête, recevoir un identifiant (essentiellement créer une ressource de critères de recherche)
  2. GET la recherche, en faisant référence à l'ID ci-dessus
  3. SUPPRIMEZ éventuellement les exigences de requête si nécessaire, mais notez qu'elles sont disponibles pour réutilisation.
Kathy Van Stone
la source
8
Merci Kathy. Je pense que je suis avec vous et je n'aime pas trop voir JSON dans l'URL. Cependant, je ne suis pas fan de faire un post pour une recherche qui est une opération GET inhérente. Pouvez-vous en citer un exemple?
whatupwilly
1
Si les requêtes peuvent fonctionner comme de simples paramètres, faites-le. La source provenait de la
Kathy Van Stone
2
Si vous voulez simplement montrer deux ressources, la réponse de James Westgate est plus typique
Kathy Van Stone
Ceci est la bonne réponse. Dans un avenir proche, je suis sûr que nous verrons un filtre = id dans (a, b, c, ...) pris en charge par OData ou quelque chose comme ça.
Bart Calixto
Voici comment fonctionne HTTP Akka
Joan
20

Première:

Je pense que vous pouvez le faire de 2 façons

http://our.api.com/Product/<id> : si vous voulez juste un disque

http://our.api.com/Product : si vous voulez tous les enregistrements

http://our.api.com/Product/<id1>,<id2> : comme James l'a suggéré peut être une option car ce qui vient après la balise Product est un paramètre

Ou celui que j'aime le plus est:

Vous pouvez utiliser la propriété Hypermedia comme moteur d'état de l'application (HATEOAS) d'un WS RestFul et effectuer un appel http://our.api.com/Productqui doit renvoyer les URL équivalentes dehttp://our.api.com/Product/<id> et les appeler après cela.

Seconde

Lorsque vous devez faire des requêtes sur les appels URL. Je suggère d'utiliser à nouveau HATEOAS.

1) Appelez le http://our.api.com/term/pumas/productType/clothing/color/black

2) Appelez le http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Utilisation de HATEOAS) Appelez ` http://our.api.com/term/pumas/productType/ -> recevez les urls tous les vêtements possibles urls -> appelez ceux que vous voulez (vêtements et sacs) - > recevoir les URL de couleur possibles -> appeler celles que vous voulez

Diego Dias
la source
1
J'ai été mis dans une situation similaire il y a quelques jours, avoir à régler une api de repos (HATEOAS) pour obtenir une (grande) liste filtrée d'objets et j'ai juste choisi votre deuxième solution. Est-ce que le rappel de l'API à plusieurs reprises pour chacun n'est pas un peu exagéré?
Samson
Cela dépend vraiment de votre système ... S'il est simple avec quelques "options", il devrait probablement être exagéré. Cependant, si vous avez des listes très volumineuses, cela peut devenir très gênant de tout faire en un seul appel, en plus si votre API est publique, cela peut devenir compliqué pour les utilisateurs (si elle est privée, cela devrait être plus facile ... apprenez aux utilisateurs que vous connaissez). Comme alternative, vous pouvez implémenter à la fois le style, le HATEOAS et un appel "tableau non reposant" pour les utilisateurs avancés
Diego Dias
Je crée un webservice api reposant dans des rails et je dois suivre la même structure d'URL que ci-dessus ( our.api.com/term/pumas/productType/clothing/color/black ). Mais je ne sais pas comment configurer les itinéraires en conséquence.
rubyist
sont le terme, le type de produit et la couleur de vos contrôleurs? Si c'est le cas, il vous suffit de faire: ressources: terme faire ressources: produitType faire ressources: couleur fin fin
Diego Dias
productType et color sont les paramètres. Donc, les paramètres de productType sont les vêtements et les paramètres des vêtements sont noirs
rubyist
12

Vous voudrez peut-être consulter la RFC 6570 . Cette spécification de modèle d'URI montre de nombreux exemples de la façon dont les URL peuvent contenir des paramètres.

Darrel Miller
la source
1
La section 3.2.8 semble être ce qui est applicable. Bien qu'il soit intéressant de noter que ce n'est qu'une proposition de norme et ne semble pas avoir dépassé ce point.
Mike Post
3
@MikePost Maintenant que l'IETF est passé à un processus de maturité en deux étapes pour les documents de «suivi des normes», je m'attends à ce que le 6570 reste ainsi pendant quelques années avant de passer à un «standard Internet». tools.ietf.org/html/rfc6410 La spécification est extrêmement stable, possède de nombreuses implémentations et est largement utilisée.
Darrel Miller,
Ah je n'étais pas au courant de ce changement. (Ou, TIL IETF est maintenant plus raisonnable.) Merci!
Mike Post
8

Premier cas:

Une recherche de produit normale ressemblerait à ceci

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

Donc je pense que la meilleure pratique serait que vous fassiez cela

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

Deuxième cas

Recherche avec des paramètres de chaîne de requête - très bien comme ça. Je serais tenté de combiner des termes avec ET et OU au lieu d'utiliser[] .

PS Cela peut être subjectif, alors faites ce avec quoi vous vous sentez à l'aise.

La raison pour laquelle les données sont placées dans l'URL est que le lien puisse être collé sur un site / partagé entre les utilisateurs. Si ce n'est pas un problème, utilisez plutôt un JSON / POST à ​​la place.

EDIT: Après réflexion, je pense que cette approche convient à une entité avec une clé composée, mais pas une requête pour plusieurs entités.

James Westgate
la source
3
Bien sûr, dans les deux exemples, la fin /ne doit pas être là car l'URI adresse une ressource, pas une collection.
Lawrence Dol
2
J'ai toujours pensé que les verbes HTTP, dans une utilisation REST, devaient faire des actions spécifiques, et c'était la ligne directrice: GET: récupérer / lire un objet, POST créer un objet, PUT mettre à jour un objet existant et DELETE supprimer un objet. Je n'utiliserais donc pas de POST pour récupérer quelque chose. Si je veux une liste d'objet en particulier (filtre), je ferais un GET avec une liste de paramètres d'URL (séparés par une virgule semble bon)
Alex
1

Je me rallierai à la réponse de nategood car elle est complète et elle semble avoir satisfait vos besoins. Cependant, je voudrais ajouter un commentaire sur l'identification de plusieurs ressources (1 ou plus) de cette façon:

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

Ce faisant, vous:

Complexifiez les clients en les forçant à interpréter votre réponse comme un tableau, ce qui pour moi est contre-intuitif si je fais la demande suivante:http://our.api.com/Product/101404

Créer des API redondantes avec une seule API pour obtenir tous les produits et celle ci-dessus pour en obtenir 1 ou plusieurs. Étant donné que vous ne devez pas montrer plus d'une page de détails à un utilisateur pour le bien de l'UX, je pense qu'avoir plus de 1 ID serait inutile et purement utilisé pour filtrer les produits.

Ce n'est peut-être pas si problématique, mais vous devrez soit le gérer vous-même côté serveur en renvoyant une seule entité (en vérifiant si votre réponse en contient une ou plusieurs), soit laisser les clients la gérer.

Exemple

Je veux commander un livre chez Amazing . Je sais exactement de quel livre il s'agit et je le vois dans la liste lors de la navigation pour les livres d'horreur:

  1. 10000 lignes incroyables, 0 test incroyable
  2. Le retour du monstre étonnant
  3. Dupliquons du code incroyable
  4. Le début étonnant de la fin

Après avoir sélectionné le deuxième livre, je suis redirigé vers une page détaillant la partie livre d'une liste:

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

Ou dans une page me donnant tous les détails de ce livre uniquement?

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

Mon avis

Je suggère d'utiliser l'ID dans la variable path lorsque l'unicité est garantie lors de l'obtention des détails de cette ressource. Par exemple, les API ci-dessous suggèrent plusieurs façons d'obtenir les détails d'une ressource spécifique (en supposant qu'un produit a un ID unique et une spécification pour ce produit a un nom unique et vous pouvez naviguer de haut en bas):

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

Au moment où vous avez besoin de plus d'une ressource, je vous suggère de filtrer à partir d'une plus grande collection. Pour le même exemple:

/products?ids=

Bien sûr, c'est mon avis car il n'est pas imposé.

gumol
la source