Pagination dans une application Web REST

329

Il s'agit d'une reformulation plus générique de cette question (avec l'élimination des parties spécifiques de Rails)

Je ne sais pas comment implémenter la pagination sur une ressource dans une application Web RESTful. En supposant que j'ai une ressource appelée products, laquelle des options suivantes pensez-vous être la meilleure approche, et pourquoi:

1. Utiliser uniquement des chaînes de requête

par exemple. http://application/products?page=2&sort_by=date&sort_how=asc
Le problème ici est que je ne peux pas utiliser la mise en cache pleine page et que l'URL n'est pas très propre et facile à retenir.

2. Utilisation des pages comme ressources et chaînes de requête pour le tri

par exemple. http://application/products/page/2?sort_by=date&sort_how=asc
Dans ce cas, le problème est que ce http://application/products/pages/1n'est pas une ressource unique car l'utilisation sort_by=pricepeut donner un résultat totalement différent et je ne peux toujours pas utiliser la mise en cache de page.

3. Utiliser des pages comme ressources et un segment d'URL pour le tri

par exemple. http://application/products/by-date/page/2
Personnellement, je ne vois aucun problème à utiliser cette méthode, mais quelqu'un m'a averti que ce n'était pas une bonne façon de procéder (il n'a pas donné de raison, donc si vous savez pourquoi ce n'est pas recommandé, faites-le moi savoir)

Toutes suggestions, opinions, critiques sont les bienvenues. Merci.

et moi
la source
34
c'est une excellente question.
Iain Holder
7
Question bonus: comment les gens spécifient-ils généralement les tailles de page?
Heiko Rupp
N'oubliez pas les paramètres de matrice w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Réponses:

66

Je pense que le problème avec la version 3 est plus un problème de "point de vue" - voyez-vous la page comme la ressource ou les produits sur la page.

Si vous voyez la page comme la ressource, c'est une solution parfaitement adaptée, car la requête pour la page 2 donnera toujours la page 2.

Mais si vous voyez les produits sur la page comme la ressource, vous avez le problème que les produits de la page 2 puissent changer (anciens produits supprimés, ou autre), dans ce cas, l'URI ne renvoie pas toujours les mêmes ressources.

Par exemple, un client stocke un lien vers la page de liste des produits X, la prochaine fois que le lien sera ouvert, le produit en question pourrait ne plus être sur la page X.

Fionn
la source
6
Eh bien, mais si vous supprimez quelque chose, il ne devrait pas y avoir autre chose sur le même URI. Si vous supprimez tous les produits de la page X - la page X peut toujours être valide mais contient maintenant les produits de la page X + 1. Ainsi, l'URI de la page X est devenu l'URI de la page X + 1 si vous le voyez dans la "vue des ressources produit" ".
Fionn
1
> Si vous voyez la page comme la ressource, c'est une solution parfaitement appropriée, car la requête de la page 2 donnera toujours la page 2. Est-ce même logique? La même URL (toute URL mentionnant la page 2) produira toujours la page 2, quelle que soit votre ressource.
temoto
2
Voir la page comme ressource devrait probablement introduire POST / foo / page pour créer une nouvelle page, non?
temoto
18
Votre réponse passe sans problème à «la bonne solution est 1», mais ne la précise pas.
temoto
2
Dans mon esprit, la page est un concept flottant et non lié au domaine sous-jacent. Et ne doit donc pas être considéré comme une ressource. Je veux dire flottant dans le sens où il est fluide, que le concept de page change avec le contexte; un utilisateur de votre API peut être une application mobile, qui ne peut consommer que 2 produits par page, tandis que l'autre est une application machine qui peut consommer toute la fichue liste. En bref, la page est une "représentation" de l'entité de domaine sous-jacente (produit) et ne doit pas être incluse comme partie de l'URL; uniquement comme paramètre de requête.
Kingz
106

Je suis d'accord avec Fionn, mais je vais aller plus loin et dire que pour moi la Page n'est pas une ressource, c'est une propriété de la demande. Cela me fait choisir la chaîne de requête de l'option 1 uniquement. Cela semble juste. J'aime vraiment la façon dont l' API Twitter est structurée de manière reposante. Pas trop simple, pas trop compliqué, bien documenté. Pour le meilleur ou pour le pire, c'est mon "go to" quand je suis sur le point de faire quelque chose d'une manière contre une autre.

slf
la source
28
+1: les chaînes de requête ne sont pas des identificateurs de ressource de première classe; ils ont juste une clarification pour la commande et le regroupement de la ressource.
S.Lott
1
@ S.Lott La demande est la ressource. Ce que vous appelez des "ressources de première classe" sont définies comme des valeurs par Fielding dans la section 5.2.1.1 de sa thèse . De plus, dans la même section, Fielding donne la dernière révision d'un fichier de code source comme exemple de ressource. Comment cela peut-il être une ressource, mais les 10 derniers produits sont des "propriétés de la demande sur la ressource produits"? Je comprends que votre point de vue est plus pratique, mais je pense qu'il est moins reposant.
edsioufi
Notez que mon commentaire ne signifie pas que je ne suis pas d'accord avec le choix d'utiliser des chaînes de requête sur les URL: les deux sont des solutions viables tant que l'API est basée sur l'hypermédia, comme @RichApodaca l'a mentionné dans sa réponse. Je souligne simplement que la page doit être considérée comme une ressource d'un point de vue REST.
edsioufi
37

HTTP a un excellent en-tête Range qui convient également à la pagination. Vous pouvez envoyer

Range: pages=1

d'avoir seulement la première page. Cela peut vous obliger à repenser ce qu'est une page. Peut-être que le client veut une gamme d'articles différente. L'en-tête de plage fonctionne également pour déclarer une commande:

Range: products-by-date=2009_03_27-

pour obtenir tous les produits plus récents que cette date ou

Range: products-by-date=0-2009_11_30

pour obtenir tous les produits antérieurs à cette date. '0' n'est probablement pas la meilleure solution, mais RFC semble vouloir quelque chose pour le début de la plage. Il peut y avoir des analyseurs HTTP déployés qui n'analyseraient pas units = -range_end.

Si les en-têtes ne sont pas une option (acceptable), je pense que la première solution (tous dans la chaîne de requête) est un moyen de traiter les pages. Mais veuillez normaliser les chaînes de requête (trier (clé = valeur) paires dans l'ordre alphabétique). Cela résout le problème de différenciation "? A = 1 & b = x" et "? B = x & a = 1".

temoto
la source
34
les en-têtes peuvent sembler agréables à première vue, mais ils interdisent le partage de la page (par exemple en copiant l'url). Donc, pour la demande ajax, cela pourrait être une bonne solution (car les pages modifiées par ajax ne peuvent pas être partagées dans leur état actuel de toute façon), mais je ne les utiliserais pas pour la pagination régulière.
Markus
3
Et l'en-tête Range est uniquement pour les plages d'octets. Voir [la spécification des en-têtes HTTP] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), section 14.35.
Chris Westin
16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 utilise des unités de plage dans les champs d'en-tête Range (section 14.35) et Content-Range (section 14.16). range-unit = bytes-unit | other-range-unit Peut-être que vous faites référence à The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.ce n'est pas la même chose que votre déclaration.
temoto
1
@Markus Je ne peux pas imaginer le cas d'utilisation lorsque vous partagez une ressource api de repos :)
JakubKnejzlik
@JakubKnejzlik Le partage n'est pas un problème, mais l'utilisation d'en-têtes HTTP pour la pagination empêche l'utilisation de liens HATEOAS pour la pagination.
xarx
25

L'option 1 semble la meilleure, dans la mesure où votre application considère la pagination comme une technique pour produire une vue différente de la même ressource.

Cela dit, le schéma d'URL est relativement insignifiant. Si vous concevez votre application de manière à être hypertexte (comme toutes les applications REST doivent l'être par définition), votre client ne construira pas d'URI seul. Au lieu de cela, votre application donnera les liens au client et le client les suivra.

Un type de lien que votre client peut fournir est un lien de pagination.

L'effet secondaire agréable de tout cela est que même si vous changez d'avis sur la structure de l'URI de pagination et implémentez quelque chose de totalement différent la semaine prochaine, vos clients peuvent continuer à travailler sans aucune modification.

Rich Apodaca
la source
3
Bon rappel sur l'utilisation de liens de type hypermédia dans les services Web REST.
Paul D. Eden
11

J'ai toujours utilisé le style de l'option 1. La mise en cache n'a pas été un problème car les données changent fréquemment de toute façon dans mon cas. Si vous autorisez la taille de la page à être configurable, les données ne peuvent pas être mises en cache.

Je ne trouve pas l'URL difficile à retenir ou impure. Pour moi, c'est une bonne utilisation des paramètres de requête. La ressource est clairement une liste de produits et les paramètres de requête indiquent simplement comment vous voulez que la liste soit affichée - triée et quelle page.

John Snyders
la source
1
+1 Je pense que vous avez raison et j'irai avec les paramètres de requête (option 1)
andi
"Je ne trouve pas l'URL difficile à retenir". Cette observation est inutile dans les applications REST, car celles-ci ne devraient généralement avoir qu'un seul signet ... Si un utilisateur (ou une application cliente) essaie de "se souvenir" de l'URL, c'est un bon signe que l'API n'est pas reposante.
edsioufi
8

Étrange que personne n'ait souligné que l'option 3 a des paramètres dans un ordre spécifique. http // application / produits / Date / Décroissant / Nom / Croissant / page / 2 et http // application / produits / Nom / Croissant / Date / Décroissant / page / 2

pointent vers la même ressource, mais ont des URL complètement différentes.

Pour moi, l'option 1 semble la plus acceptable, car elle sépare clairement "ce que je veux" et "comment je veux" (elle a même un point d'interrogation entre eux lol). La mise en cache pleine page peut être implémentée à l'aide de l'URL complète (toutes les options subiront le même problème de toute façon).

Avec l'approche des paramètres dans l'URL, le seul avantage est une URL propre. Bien que vous deviez trouver un moyen d'encoder les paramètres et de les décoder sans perte. Bien sûr, vous pouvez aller avec URLencode / decode, mais cela rendra les URL laides à nouveau :)

TEHEK
la source
1
Ce sont deux commandes différentes. Le premier trie par date décroissante et ne rompt que les liens par nom croissant; le second trie par nom croissant et ne rompt les liens que par date décroissante.
Imran Rashid
En fait, les deux exemples d'URL donnés ici ne sont pas seulement différents par leur écriture, mais aussi par leur signification. Depuis dénotant un chemin, aucune garantie n'est donnée que vous trouviez la même chose lorsque vous tournez à gauche en premier et à droite ensuite ou vice versa. Cela dit, les paramètres de tri en tant que parties de chemin d'URL ont des avantages formels par rapport aux paramètres d'URL qui devraient être échangeables de manière commutative sans changer la signification globale, mais souffrent en effet de pièges de codage comme cela est dit ici.
Christian Gosch
7

Je préfère utiliser les paramètres de requête offset et limit.

offset : pour l'index de l'élément dans la collection.

limite : pour le nombre d'articles.

Le client peut simplement continuer à mettre à jour l'offset comme suit

offset = offset + limit

pour la page suivante.

Le chemin est considéré comme l'identifiant de la ressource. Et une page n'est pas une ressource mais un sous-ensemble de la collection de ressources. Étant donné que la pagination est généralement une demande GET, les paramètres de requête conviennent mieux à la pagination qu'aux en-têtes.

Référence: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page

Trieur
la source
5

À la recherche des meilleures pratiques, je suis tombé sur ce site:

http://www.restapitutorial.com

Dans la page des ressources, il y a un lien pour télécharger un .pdf qui contient les meilleures pratiques REST complètes suggérées par l'auteur. Dans lequel, entre autres, il y a une section sur la pagination.

L'auteur suggère d'ajouter un support à la fois en utilisant un en-tête Range et en utilisant des paramètres de chaîne de requête.

Demande

Exemple d'en-tête HTTP:

Range: items=0-24

Exemple de paramètres de chaîne de requête:

GET http://api.example.com/resources?offset=0&limit=25

offset est le numéro d'article de début et limit est le nombre maximum d'articles à retourner.

Réponse

La réponse doit inclure un en-tête Content-Range indiquant le nombre d'éléments retournés et le nombre total d'éléments qui restent à récupérer.

Exemples d'en-tête HTTP:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

Dans le .pdf il y a quelques autres suggestions pour des cas plus spécifiques.

Mario Arturo
la source
4

J'utilise actuellement un schéma similaire à celui-ci dans mes applications ASP.NET MVC:

par exemple http://application/products/by-date/page/2

spécifiquement c'est: http://application/products/Date/Ascending/3

Cependant, je ne suis pas vraiment heureux d'inclure des informations de pagination et de tri dans l'itinéraire de cette manière.

La liste des articles (produits dans ce cas) est modifiable. c'est-à-dire que la prochaine fois que quelqu'un reviendra à une URL qui inclut des paramètres de pagination et de tri, les résultats qu'ils obtiendront auront peut-être changé. L'idée d' http://application/products/Date/Ascending/3une URL unique pointant vers un ensemble de produits défini et immuable est donc perdue.

Steve Willcock
la source
1
Le premier problème, avec le tri sur plusieurs colonnes, s'applique à toutes les 3 méthodes à mon avis. Ce n'est donc pas vraiment un pour / un pour aucun d'entre eux. Concernant le deuxième problème: cela ne peut-il arriver à aucune ressource? Un produit, par exemple, peut également être modifié / supprimé.
andi
Je pense que le tri sur plusieurs colonnes est vraiment un `` con '' pour les 3 méthodes, car l'URL devient simplement plus grande et plus ingérable - d'où une raison pour laquelle j'envisage de passer à des paramètres de page / tri basés sur un formulaire. Pour le deuxième problème, je pense qu'il y a une différence conceptuelle fondamentale entre un identifiant persistant unique comme un identifiant de produit et une liste transitoire de produits. Pour les produits supprimés, un message, par exemple «Ce produit n'existe pas dans le système», vous indique quelque chose de concret à propos de ce produit.
Steve Willcock
1
Il est bon de supprimer toutes les informations de pagination et de tri de l'itinéraire. Et le pousser dans les paramètres POST est mauvais. Bonjour? La question concerne REST. Nous n'utilisons pas POST uniquement pour raccourcir l'URL dans REST. Le verbe a du sens.
temoto
1
Personnellement, je n'utiliserais pas de paramètres de formulaire pour une requête car cela nécessiterait presque une méthode HTTP POST ou PUT (car il y a maintenant un corps dans la requête). GET me semble être la méthode la plus appropriée à utiliser puisque POST et PUT impliquent de modifier la ressource. Pour cette raison, j'irais avec l'ajout de paramètres de requête à l'URL lorsqu'un tri par plusieurs colonnes est nécessaire.
Paul D. Eden
1

J'ai tendance à être d'accord avec slf que la "page" n'est pas vraiment une ressource. D'un autre côté, l'option 3 est plus propre, plus facile à lire et peut être plus facilement devinée par l'utilisateur et même tapée si nécessaire. Je suis déchiré entre les options 1 et 3, mais je ne vois aucune raison de ne pas utiliser l'option 3.

En outre, bien qu'ils semblent agréables, l'un des inconvénients de l'utilisation de paramètres cachés, comme quelqu'un l'a mentionné, plutôt que de chaînes de requête ou de segments d'URL, est que l'utilisateur ne peut pas créer de signet ou créer un lien direct vers une page particulière. Cela peut ou non être un problème selon l'application, mais juste quelque chose à savoir.

insane.dreamer
la source
1
En ce qui concerne votre mention d'être plus facile à deviner, cela ne devrait pas avoir d'importance. Si vous créez une API hypermédia, les utilisateurs ne devraient jamais avoir à deviner les URI.
JR Garcia
0

J'ai déjà utilisé la solution 3 (j'écris BEAUCOUP d'applications Django). Et je ne pense pas qu'il y ait quelque chose de mal à cela. Il est tout aussi générable que les deux autres (au cas où vous devriez faire un grattage de masse ou similaire) et il semble plus propre. De plus, vos utilisateurs peuvent deviner les URL (s'il s'agit d'une application accessible au public), et les gens aiment pouvoir aller directement où ils veulent, et la devinette des URL est valorisante.

Alex
la source
0

J'utilise dans mes projets les URL suivantes:

http://application/products?page=2&sort=+field1-field2

ce qui signifie - "donnez-moi la page la deuxième page ordonnée ascendante par champ1 puis décroissante par champ2". Ou si j'ai besoin de plus de flexibilité, j'utilise:

http://application/products?skip=20&limit=20&sort=+field1-field2
Eugène
la source
0

J'utilise dans les modèles suivants pour obtenir l'enregistrement de la page suivante. http: // application / produits? lastRecordKey =? & pageSize = 20 & sort = ASC

RecordKey est la colonne d'une table qui contient une valeur séquentielle dans DB. Il est utilisé pour extraire une seule page à la fois de la base de données. pageSize est utilisé pour déterminer le nombre d'enregistrements à récupérer. sort est utilisé pour trier l'enregistrement dans l'ordre croissant ou décroissant.

Susanta Ghosh
la source