312 votes

Meilleures pratiques en matière de pagination de l'API

J'aimerais avoir de l'aide pour gérer un cas limite étrange avec une API paginée que je suis en train de créer.

Comme beaucoup d'API, celle-ci pagine les grands résultats. Si vous interrogez /foos, vous obtiendrez 100 résultats (c'est-à-dire foo #1-100), et un lien vers /foos?page=2 qui devrait retourner foo #101-200.

Malheureusement, si foos #10 est supprimé de l'ensemble de données avant que le consommateur d'API n'effectue la requête suivante, /foos?page=2 sera décalé de 100 et renverra foos #102-201.

C'est un problème pour les consommateurs de l'API qui essaient de tirer tous les foo - ils ne recevront pas le foo #101.

Quelle est la meilleure pratique pour gérer cela ? Nous aimerions que ce soit aussi léger que possible (c'est-à-dire éviter de gérer les sessions pour les demandes d'API). Des exemples d'autres API seraient très appréciés !

1 votes

Quel est le problème ici ? cela me semble correct, de toute façon l'utilisateur aura 100 articles.

0 votes

Je viens d'éditer la question - le problème est que le foo #101 n'apparaîtra pas dans les résultats et qu'un consommateur d'API essayant d'extraire tous les foos en manquera un.

2 votes

J'ai été confronté à ce même problème et je cherche une solution. A ma connaissance, il n'y a pas vraiment de mécanisme solide et garanti pour accomplir ceci, si chaque page exécute une nouvelle requête. La seule solution à laquelle je pense est de garder une session active, et de conserver l'ensemble des résultats côté serveur, et plutôt que d'exécuter de nouvelles requêtes pour chaque page, de simplement récupérer le prochain ensemble d'enregistrements mis en cache.

189voto

jandjorgensen Points 2984

Je ne suis pas tout à fait sûr de la façon dont vos données sont traitées, donc cela peut ou non fonctionner, mais avez-vous envisagé de paginer avec un champ d'horodatage ?

Lorsque vous interrogez /foos, vous obtenez 100 résultats. Votre API devrait alors renvoyer quelque chose comme ceci (en supposant JSON, mais si elle a besoin de XML, les mêmes principes peuvent être suivis) :

{
    "data" : [
        {  data item 1 with all relevant fields    },
        {  data item 2   },
        ...
        {  data item 100 }
    ],
    "paging":  {
        "previous":  "http://api.example.com/foo?since=TIMESTAMP1" 
        "next":  "http://api.example.com/foo?since=TIMESTAMP2"
    }

}

Notez que l'utilisation d'un seul horodatage implique une "limite" implicite dans vos résultats. Il est possible d'ajouter une limite explicite ou d'utiliser une méthode d'analyse de type until propriété.

L'horodatage peut être déterminé dynamiquement en utilisant le dernier élément de données de la liste. Il semble que ce soit plus ou moins la façon dont Facebook pagine dans sa page API graphique (faites défiler jusqu'en bas pour voir les liens de pagination dans le format que j'ai donné ci-dessus).

Un problème peut se poser si vous ajoutez un élément de données, mais d'après votre description, il semble qu'ils seraient ajoutés à la fin (si ce n'est pas le cas, faites-le moi savoir et je verrai si je peux améliorer cela).

36 votes

L'unicité de l'horodatage n'est pas garantie. En d'autres termes, plusieurs ressources peuvent être créées avec le même horodatage. Cette approche présente donc l'inconvénient que la page suivante pourrait répéter les dernières (quelques ?) entrées de la page actuelle.

4 votes

@prmatta En fait, selon l'implémentation de la base de données un timestamp est garanti comme étant unique .

3 votes

@jandjorgensen De votre lien : "Le type de données timestamp est juste un nombre incrémentiel et ne préserve pas une date ou une heure. ... Dans le serveur SQL 2008 et les versions ultérieures, le le type de timestamp a été renommé en rowversion sans doute pour mieux refléter son objectif et sa valeur." Il n'y a donc aucune preuve ici que les timestamps (ceux qui contiennent réellement une valeur temporelle) sont uniques.

30voto

kamilk Points 534

Si vous avez une pagination, vous pouvez également trier les données par clé. Pourquoi ne pas laisser les clients de l'API inclure la clé du dernier élément de la collection précédemment retournée dans l'URL et ajouter une balise WHERE à votre requête SQL (ou quelque chose d'équivalent, si vous n'utilisez pas SQL) afin qu'elle ne renvoie que les éléments pour lesquels la clé est supérieure à cette valeur ?

4 votes

Ce n'est pas une mauvaise suggestion, mais ce n'est pas parce que vous triez par une valeur que celle-ci est une "clé", c'est-à-dire unique.

0 votes

Exactement. Par exemple, dans mon cas, le champ de tri se trouve être une date, et elle est loin d'être unique.

29voto

Will Hartung Points 57465

Vous avez plusieurs problèmes.

Tout d'abord, vous avez l'exemple que vous avez cité.

Vous avez également un problème similaire si des lignes sont insérées, mais dans ce cas, l'utilisateur obtient des données en double (sans doute plus facile à gérer que des données manquantes, mais cela reste un problème).

Si vous n'effectuez pas de snapshot de l'ensemble de données original, c'est une réalité.

Vous pouvez demander à l'utilisateur de faire un cliché explicite :

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

Ce qui résulte :

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

Vous pouvez ensuite le mettre en page toute la journée, puisqu'il est maintenant statique. Cela peut être raisonnablement léger, puisque vous pouvez juste capturer les clés du document réel plutôt que les rangées entières.

Si le cas d'utilisation est simplement que vos utilisateurs veulent (et ont besoin) de toutes les données, alors vous pouvez simplement les leur donner :

GET /query/12345?all=true

et envoyer le kit complet.

1 votes

(Le tri par défaut de foos se fait par date de création, l'insertion de lignes ne pose donc pas de problème).

1 votes

En fait, capturer uniquement les clés des documents n'est pas suffisant. De cette façon, vous devrez interroger les objets complets par ID lorsque l'utilisateur les demandera, mais il se peut qu'ils n'existent plus.

15voto

Brent Baisley Points 10223

Il peut être difficile de trouver les meilleures pratiques, car la plupart des systèmes dotés d'API ne tiennent pas compte de ce scénario, parce qu'il s'agit d'un cas extrême, ou parce qu'ils ne suppriment généralement pas les enregistrements (Facebook, Twitter). Facebook précise que chaque "page" peut ne pas contenir le nombre de résultats demandés en raison du filtrage effectué après la pagination. https://developers.facebook.com/blog/post/478/

Si vous devez vraiment tenir compte de ce cas limite, vous devez vous "souvenir" de l'endroit où vous vous êtes arrêté. La suggestion de jandjorgensen est tout à fait pertinente, mais j'utiliserais un champ dont l'unicité est garantie, comme la clé primaire. Vous devrez peut-être utiliser plus d'un champ.

En suivant le flux de Facebook, vous pouvez (et devriez) mettre en cache les pages déjà demandées et ne renvoyer que celles dont les rangs ont été supprimés et filtrés s'ils demandent une page qu'ils avaient déjà demandée.

2 votes

Ce n'est pas une solution acceptable. Elle consomme beaucoup de temps et de mémoire. Toutes les données supprimées ainsi que les données demandées devront être conservées en mémoire, ce qui pourrait ne pas être utilisé du tout si le même utilisateur ne demande plus d'entrées.

3 votes

Je ne suis pas d'accord. Le simple fait de conserver les identifiants uniques n'utilise pas beaucoup de mémoire. Vous n'avez pas besoin de conserver les données indéfiniment, juste pour la "session". C'est facile avec memcache, il suffit de définir la durée d'expiration (par exemple 10 minutes).

1 votes

La mémoire est moins chère que la vitesse du réseau/du processeur. Donc, si la création d'une page est très coûteuse (en termes de réseau ou de CPU), alors la mise en cache des résultats est une approche valable @DeepakGarg

2voto

moskiteau Points 817

Eh bien, vous faites tout de travers. La pagination, même une API, doit faire abstraction des identifiants. C'est pourquoi, vous devez utiliser un maximum et un décalage.

foo.com/foos?max=100&offset=0 -> return the first 100 foo's
foo.com/foos?max=100&offset=100 -> return the foo's from 100 to 200 regardless of the ids

Et vous revenez :

data -> obviously
total -> so we know we can ask for more
offset -> ...
max -> ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X