web-dev-qa-db-fra.com

Comment les recherches s'intègrent-elles dans une interface RESTful?

Lors de la conception d'une interface RESTful, la sémantique des types de demande est considérée comme vitale pour la conception.

  • [~ # ~] get [~ # ~] - Liste la collection ou récupère l'élément
  • [~ # ~] put [~ # ~] - Remplace la collection ou l'élément
  • [~ # ~] post [~ # ~] - Créer une collection ou un élément
  • [~ # ~] delete [~ # ~] - Eh bien, euh, supprimez la collection ou l'élément

Cependant, cela ne semble pas couvrir le concept de "recherche".

Par exemple. lors de la conception d'une suite de services Web prenant en charge un site de recherche d'emploi, vous pourriez avoir les exigences suivantes:

  • Obtenez une annonce d'emploi individuelle
    • [~ # ~] obtenez [~ # ~] à domain/Job/{id}/
  • Créer une annonce d'emploi
    • [~ # ~] publier [~ # ~] dans domain/Job/
  • Mettre à jour l'annonce d'emploi
    • [~ # ~] mettez [~ # ~] sur domain/Job/
  • Supprimer l'annonce d'emploi
    • [~ # ~] supprimer [~ # ~] à domain/Job/

"Obtenir tous les emplois" est également simple:

  • [~ # ~] obtenez [~ # ~] à domain/Jobs/

Cependant, comment la "recherche" d'emploi s'inscrit-elle dans cette structure?

Vous pourriez prétendre qu'il s'agit d'une variante de la "collection de listes" et implémenter comme:

  • [~ # ~] obtenez [~ # ~] à domain/Jobs/

Cependant, les recherches peuvent être complexes et il est tout à fait possible de produire une recherche qui génère une longue chaîne GET. Autrement dit, en référençant ne SO ici , il y a des problèmes d'utilisation de chaînes GET de plus de 2000 caractères environ.

Un exemple peut être dans une recherche à facettes - en continuant l'exemple "job".

Je peux autoriser la recherche sur les facettes - "Technologie", "Titre du poste", "Discipline" ainsi que les mots clés en texte libre, l'âge de l'emploi, le lieu et le salaire.

Avec une interface utilisateur fluide et un grand nombre de technologies et de titres de poste, il est possible qu'une recherche puisse englober un grand nombre de choix de facettes.

Ajustez cet exemple aux CV, plutôt qu'aux emplois, apportez encore plus de facettes, et vous pouvez très facilement imaginer une recherche avec une centaine de facettes sélectionnées, ou même seulement 40 facettes chacune de 50 caractères (par exemple, les titres de poste, les noms d'université, Noms des employeurs).

Dans cette situation, il peut être souhaitable de déplacer un PUT ou POST afin de garantir que les données de recherche seront correctement envoyées. Par exemple:

  • [~ # ~] publier [~ # ~] dans domain/Jobs/

Mais sémantiquement, c'est une instruction pour créer une collection.

Vous pourriez dites également que vous l'exprimerez comme la création d'une recherche:

  • [~ # ~] publier [~ # ~] dans domain/Jobs/Search/

ou (comme suggéré par le programme de gravure ci-dessous)

  • [~ # ~] publier [~ # ~] dans domain/JobSearch/

Sémantiquement, cela peut sembler logique, mais vous ne créez rien, vous faites une demande de données.

Donc, sémantiquement, c'est un [~ # ~] get [~ # ~] , mais [~ # ~] get [~ # ~] n'est pas garanti pour prendre en charge ce dont vous avez besoin.

Donc, la question est - En essayant de rester aussi fidèle que possible à la conception RESTful, tout en veillant à respecter les limites de HTTP, quelle est la conception la plus appropriée pour une recherche?

153
Rob Baillie

Vous ne devez pas oublier que [~ # ~] obtenir [~ # ~] les demandes ont des avantages supérieurs par rapport à d'autres solutions:

1) Les requêtes GET peuvent être copiées à partir de la barre d'URL, elles sont digérées par les moteurs de recherche, elles sont "amicales". Où "amicales" signifie que normalement une requête GET ne devrait rien modifier dans votre application (idempotent) . C'est le cas standard pour une recherche.

2) Tous ces concepts sont très importants non seulement de l'utilisateur et du moteur de recherche, mais d'une architecture API conception point de vue.

3) Si vous créez une solution de contournement avec POST/PUT , vous aurez des problèmes auxquels vous ne pensez pas en ce moment. Par exemple, dans le cas d'un navigateur, le bouton de navigation/actualiser la page/l'historique. Ceux-ci peuvent être résolus bien sûr, mais cela va être une autre solution de contournement, puis une autre et une autre ...

Compte tenu de tout cela, mon conseil serait:

a) Vous devriez pouvoir rentrer dans votre GET avec en utilisant structure de paramètres intelligente . Dans les cas extrêmes, vous pouvez même opter pour des tactiques comme cette recherche Google où j'ai défini beaucoup de paramètres, c'est toujours une URL super courte.

b) Créez une autre entité dans votre application comme JobSearch . En supposant que vous disposiez de tant d'options, il est probable que vous devrez également stocker ces recherches et les gérer, de sorte que cela clarifie simplement votre application. Vous pouvez travailler avec les objets JobSearch comme une entité entière, ce qui signifie que vous pouvez le tester /utilisez-le plus facilement .


Personnellement, j'essaierais de me battre avec toutes mes griffes pour le faire avec a) et quand tout espoir est perdu, je ramperais avec les larmes aux yeux à l'option b) .

101
p1100i

TL; DR: GET pour le filtrage, POST pour la recherche

Je fais une distinction entre filtrer les résultats de la liste d'une collection par rapport à une recherche complexe. Le test décisif que j'utilise est essentiellement si j'ai besoin de plus que du filtrage (positif, négatif ou plage) Je considère que c'est une recherche plus complexe nécessitant POST.

Cela tend à être renforcé lorsque l'on pense à ce qui sera retourné. J'utilise généralement GET uniquement si une ressource a un cycle de vie presque complet (PUT, DELETE, GET, collection GET) . Généralement, dans une collection GET, je renvoie une liste d'URI qui sont les ressources REST qui composent cette collection. Dans une requête complexe, je peux tirer de plusieurs ressources afin de construire la réponse (pensez à la jointure SQL) donc je ne renverrai pas d'URI, mais des données réelles. Le problème est que les données ne seront pas représentées dans une ressource, donc je devrai toujours renvoyer des données. Cela me semble un cas très clair d'exiger un POST.

-

Cela faisait longtemps et mon message d'origine était un peu bâclé, alors j'ai pensé que je mettrais à jour.

GET est le choix intuitif pour renvoyer la plupart des types de données, des collections de REST, des données structurées d'une ressource, même des charges utiles singulières (images, documents, etc.).

POST est la méthode catchall pour tout ce qui ne semble pas correspondre à GET, PUT, DELETE, etc.

À ce stade, je pense que les recherches simples, le filtrage ont un sens intuitif via GET. Les recherches complexes dépendent de vos préférences personnelles, surtout si vous lancez des fonctions d'agrégation, des corrélations croisées (jointures), des reformateurs, etc. Je dirais que les paramètres GET ne devraient pas être trop longs et qu'il s'agit d'une requête plus large (mais elle est structurée ) peut souvent avoir plus de sens en tant que corps de requête POST.

Je considère également l'aspect expérience de l'utilisation des API. Je veux généralement rendre la plupart des méthodes aussi faciles à utiliser et intuitives que possible. Je vais pousser des appels qui sont plus flexibles (et donc plus complexes) dans des POST et sur un URI de ressource différent, surtout s'il est incompatible avec le comportement d'autres ressources REST dans la même API).

Quoi qu'il en soit, la cohérence est probablement plus importante que si vous effectuez une recherche dans GET ou POST.

J'espère que cela t'aides.

13
dietbuddha

Dans REST, la définition des ressources est très large. C'est vraiment comme vous voulez regrouper certaines données.

  • Il est utile de considérer une ressource de recherche comme une ressource de collection. Les paramètres de requête, parfois appelés la partie consultable de l'URI, limitent la ressource aux éléments qui intéressent le client.

Par exemple, l'URI principal de Google pointe vers une ressource de collecte de "liens vers tous les sites sur Internet". Les paramètres de requête restreignent cela aux sites que vous souhaitez voir.

(URI = identificateur de ressource universel, dont URL = localisateur de ressource universel, où le "http: //" familier est le format par défaut d'un URI. L'URL est donc un localisateur, mais en REST il est bon de généraliser cela à un identifiant de ressource. Les gens les utilisent cependant de manière interchangeable.)

  • Étant donné que la ressource que vous recherchez dans votre exemple est la collection d'emplois, il est logique de rechercher avec

GET site/jobs? Type = blah & location = here & etc = etc

(retour) {jobs: [{job: ...}]}

Et puis utilisez POST, qui est le verbe append ou process pour ajouter de nouveaux éléments à cette collection:

Site POST/emplois

{emploi: ...}

  • Notez que c'est la même structure pour l'objet job dans chaque cas. Un client peut obtenir une collection de travaux, en utilisant des paramètres de requête pour affiner la recherche, puis utiliser le même format pour l'un des éléments pour POST un nouveau travail. Ou il peut prendre l'un de ces éléments et PUT à son URI pour mettre à jour celui-ci.

  • Pour les chaînes de requête très longues ou compliquées, la convention permet d'envoyer celles-ci sous la forme de requêtes POST à la place. Regroupez les paramètres de requête sous forme de paires nom/valeur ou d'objets imbriqués dans une structure JSON ou XML et l'envoyer dans le corps de la demande. Par exemple, si votre requête contient des données imbriquées au lieu d'un groupe de paires nom/valeur. La spécification HTTP pour POST la décrit comme le verbe ajouter ou traiter) (Si vous voulez naviguer sur un cuirassé à travers une faille dans REST, utilisez POST.)

J'utiliserais cela comme plan de secours, cependant.

Ce que vous perdez quand vous faites cela, c'est a) GET est nullipotent - c'est-à-dire qu'il ne change rien - POST ne l'est pas. Donc, si l'appel échoue, le middleware ne le fera pas réessayez ou cachez automatiquement les résultats, et 2) avec les paramètres de recherche dans le corps, vous ne pouvez plus couper et coller l'URI. Autrement dit, l'URI n'est pas un identifiant spécifique pour la recherche que vous souhaitez.

Faire la différence entre "créer" et "rechercher". Il existe quelques options compatibles avec REST practice:

  • Vous pouvez le faire dans l'URI en ajoutant quelque chose au nom de la collection, comme la recherche d'emploi au lieu de travaux. Cela signifie simplement que vous traitez la collection de recherche comme une ressource distincte.

  • Étant donné que la sémantique de POST est à la fois ajouter OR processus, vous pouvez identifier les corps de recherche avec la charge utile. Comme {job: ...} vs. {search : ...}. C'est à la logique POST de la publier ou de la traiter de manière appropriée.

C'est à peu près une préférence de conception/mise en œuvre. Je ne pense pas qu'il y ait une convention claire.

Donc, comme vous l'avez déjà expliqué, l'idée est de définir une ressource de collection pour jobs

site/emplois

Recherchez avec les paramètres de requête GET + pour affiner la recherche. Les requêtes de données longues ou structurées entrent dans le corps d'un POST (éventuellement vers une collection de recherche distincte). Créez avec POST pour l'ajouter à la collection. Et mettez à jour avec PUT vers un URI spécifique.

(FWIW, la convention de style avec les URI est d'utiliser tous les minuscules avec des mots séparés par des tirets. Mais cela ne signifie pas que vous devez le faire de cette façon.)

(De plus, je dois dire que d'après votre question, il est clair que vous êtes loin dans cette voie. J'ai énoncé les choses de manière explicite juste pour les aligner, mais votre question avait déjà abordé la plupart des problèmes sémantiques dans ce Je venais juste de la relier à quelques conventions et pratiques.)

10
Rob

J'utilise généralement des requêtes OData, elles fonctionnent comme un appel GET mais vous permettent de restreindre les propriétés qui sont retournées et de les filtrer.

Vous utilisez des jetons tels que $select= et $filter= donc vous vous retrouverez avec un URI qui ressemble à ceci:

/users?$select=Id,Name$filter=endswith(Name, 'Smith')

Vous pouvez également effectuer une pagination à l'aide de $skip et $top et commande.

Pour plus d'informations, consultez OData.org . Vous n'avez pas spécifié la langue que vous utilisez, mais s'il s'agit d'ASP.NET, la plate-forme WebApi prend en charge les requêtes OData - pour d'autres (PHP, etc.), il existe probablement des bibliothèques que vous pouvez utiliser pour les traduire en requêtes de base de données.

8
Trevor Pilley

C'est une vieille réponse mais je peux quand même contribuer un peu à la discussion. J'ai observé très souvent une incompréhension de REST, RESTful et Architecture. RESTful ne mentionne jamais rien sur la recherche de construction NON, il n'y a rien dans RESTful sur l'architecture, c'est un ensemble de principes ou de critères de conception.

Pour mieux décrire une recherche, nous devons parler d'une architecture en particulier et celle qui correspond le mieux est l'architecture orientée ressources (ROA).

Dans RESTful, il existe des principes à concevoir, idempotent ne signifie pas que le résultat ne peut pas changer comme je l'ai lu dans certaines réponses, cela signifie que le résultat d'une demande indépendante ne dépend pas du nombre de fois exécuté. Cela peut changer, imaginons que je mets à jour en permanence une base de données en l'alimentant avec des données qui sont servies par un Api RESTful, l'exécution du même GET peut changer le résultat mais cela ne dépend pas du nombre de fois où il a été exécuté. Si je suis capable de geler le monde, cela signifie qu'il n'y a pas d'état, de transformation, quoi que ce soit à l'intérieur du service lorsque je demande la ressource qui mène à un résultat différent.

Par définition, une ressource est tout ce qui est important d'être référencé comme une chose en soi.

Dans une architecture orientée ressources (appelons-la désormais ROA par souci de concision) nous nous concentrons sur la ressource qui pourrait être beaucoup de choses:

  • Une version d'un document
  • La dernière version mise à jour du document
  • Un résultat issu d'une recherche
  • Une liste d'objets
  • Le premier article que j'ai acheté sur un e-commerce

Ce qui le rend unique en termes de ressources est l'adressabilité ce qui signifie qu'il n'a qu'un seul URI

De cette façon, la recherche s'intègre parfaitement dans RESTful compte tenu du ROA . Nous devons utiliser GET parce que je suppose que votre recherche est une recherche normale et qu'elle ne change rien, elle est donc idempotente (même si elle renvoie des choses différentes en fonction des nouveaux éléments ajoutés). Il y a une confusion ici de cette façon parce que je pourrais m'en tenir à RESTful et non au ROA, cela signifie que je pourrais suivre un modèle qui crée une recherche et retourner différentes choses avec les mêmes paramètres parce que je n'utilise pas le principe d'adressabilité du ROA. Comment c'est? Eh bien, si vous envoyez les filtres de recherche dans le corps ou l'en-tête, la ressource n'est pas ADRESSE.

Vous pouvez trouver les principes de ce qui est exactement et de l'URI dans le document original W3:

https://www.w3.org/DesignIssues/Axioms

Toute URL de cette architecture doit être auto-descriptive. C'est nécessaire si vous suivez les principes pour tout traiter dans l'URI, cela signifie que vous pouvez utiliser/(barre oblique) pour séparer tout ce dont vous avez besoin ou interroger les paramètres. Nous savons qu'il y a des limites à cela, mais c'est le modèle d'architecture.

Suivant le modèle de ROA dans RESTful, une recherche n'est pas plus que n'importe quelle autre ressource, la seule différence est que les ressources proviennent d'un calcul au lieu d'une relation directe avec l'objet lui-même. Sur la base du principe, je pourrais aborder et obtenir un service de calcul arithmétique simple basé sur le modèle suivant:

http://myapi.com/sum/1/2

Là où somme, 1 et 2 peuvent être modifiés mais le résultat du calcul est unique et adressable, chaque fois que j'appelle avec les mêmes paramètres j'obtiens les mêmes et rien ne change dans le service. Les ressources/somme/1/2 et/soustrait/5/4 respectent parfaitement les principes.

7
Maximiliano Rios

Une approche à considérer consiste à traiter l'ensemble des requêtes possibles comme une ressource de collecte, par exemple /jobs/filters.

POST les demandes à cette ressource, avec les paramètres de requête dans le corps, créeront une nouvelle ressource ou identifieront un filtre équivalent existant et renverront une URL contenant son ID: /jobs/filters/12345.

L'ID peut ensuite être utilisé dans une demande GET pour les travaux: /jobs?filter=12345. Les requêtes GET suivantes sur la ressource de filtre renverront la définition du filtre.

Cette approche a l'avantage de vous libérer du format de paramètre de requête pour la définition de filtre, vous offrant potentiellement plus de pouvoir pour définir des filtres complexes. OR les conditions sont un exemple que je peux imaginer difficile à accomplir avec les chaînes de requête.

Un inconvénient de cette approche est que vous perdez la lisibilité de l'URL (bien que cela puisse être atténué en récupérant la définition via une requête GET pour la ressource de filtre). Pour cette raison, vous pouvez également prendre en charge le même ou un sous-ensemble des paramètres de requête sur le /jobs ressource comme vous le feriez pour une ressource de filtre. Cela pourrait être utilisé pour des requêtes plus courtes. Si cette fonctionnalité est fournie, afin de maintenir la mise en cache entre les deux types de filtrage, lors de l'utilisation des paramètres de requête sur le /jobs, l'implémentation doit créer/réutiliser en interne une ressource de filtre et renvoyer un 302 ou 303 état indiquant l'URL sous la forme de /jobs?filter=12345.

5
pgraham

GET est correct, si vous avez une collection statique qui renvoie toujours les mêmes résultats (représentation) pour un URI. Cela implique également que les données générant ces représentations ne sont jamais modifiées. La source est une base de données en lecture seule.

Avoir GET renvoie des résultats différents pour un seul et même URI viole idempotency/safety et le principe CoolURI et n'est par conséquent pas RESTful . Il est possible que des verbes idempotents écrivent dans une base de données, mais ils ne doivent jamais affecter la représentation.

Une recherche courante commence par une demande POST qui renvoie une référence au résultat. Elle génère le résultat (il est nouveau et peut être récupéré avec un GET ultérieur). Ce résultat peut être hiérarchique (plus des références avec des URI que vous pouvez OBTENIR), bien sûr, et pourraient réutiliser des éléments de recherches antérieures, si cela a du sens pour l'application.

Soit dit en passant, je sais que les gens le font différemment. Vous n'avez pas besoin de m'expliquer à quel point il peut être pratique de violer REST.

3
Martin Sugioarto