J'adorerais avoir de l'aide pour gérer un cas de bord étrange avec une API paginée que je construis.
Comme de nombreuses API, celle-ci affiche de 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 foo # 10 est supprimé de l'ensemble de données avant que le consommateur d'API ne fasse la prochaine requête, / foos? Page = 2 sera compensé par 100 et renverra foos # 102-201.
C'est un problème pour les consommateurs d'API qui essaient de tirer tous les foos - ils ne recevront pas foo # 101.
Quelle est la meilleure pratique pour gérer cela? Nous aimerions le rendre aussi léger que possible (c'est-à-dire en évitant de gérer les sessions pour les demandes d'API). Des exemples provenant d'autres API seraient grandement appréciés!
la source
Réponses:
Je ne suis pas complètement 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 retourner quelque chose comme ça (en supposant JSON, mais si elle a besoin de XML, les mêmes principes peuvent être suivis):
Juste une note, l'utilisation d'un seul horodatage repose sur une «limite» implicite dans vos résultats. Vous pouvez ajouter une limite explicite ou utiliser également un
until
propriété.L'horodatage peut être déterminé dynamiquement à l'aide du dernier élément de données de la liste. Cela semble être plus ou moins la façon dont Facebook pagine dans son API graphique (faites défiler vers le bas pour voir les liens de pagination dans le format que j'ai donné ci-dessus).
Un problème peut être si vous ajoutez un élément de données, mais d'après votre description, il semble qu'ils seraient ajoutés à la fin (sinon, faites-le moi savoir et je verrai si je peux améliorer cela).
la source
Vous avez plusieurs problèmes.
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 les données manquantes, mais reste un problème).
Si vous ne prenez pas un instantané de l'ensemble de données d'origine, ce n'est qu'une réalité.
Vous pouvez demander à l'utilisateur de créer un instantané explicite:
Quels résultats:
Ensuite, vous pouvez la page toute la journée, car elle est désormais statique. Cela peut être relativement léger, car vous pouvez simplement capturer les clés de document réelles plutôt que les lignes entières.
Si le cas d'utilisation est simplement que vos utilisateurs veulent (et ont besoin) de toutes les données, vous pouvez simplement leur donner:
et envoyez simplement le kit complet.
la source
Si vous avez la pagination, vous triez également les données par clé. Pourquoi ne pas laisser les clients API inclure la clé du dernier élément de la collection retournée précédemment dans l'URL et ajouter une
WHERE
clause à 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?la source
Il peut y avoir deux approches en fonction de votre logique côté serveur.
Approche 1: lorsque le serveur n'est pas suffisamment intelligent pour gérer les états des objets.
Vous pouvez envoyer tous les identifiants uniques des enregistrements mis en cache au serveur, par exemple ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] et un paramètre booléen pour savoir si vous demandez de nouveaux enregistrements (tirer pour actualiser) ou d'anciens enregistrements (charger plus).
Votre serveur devrait être responsable de renvoyer les nouveaux enregistrements (charger plus d'enregistrements ou de nouveaux enregistrements via pull pour actualiser) ainsi que les identifiants des enregistrements supprimés de ["id1", "id2", "id3", "id4", "id5", " id6 "," id7 "," id8 "," id9 "," id10 "].
Exemple: - Si vous demandez plus de chargement, votre demande devrait ressembler à ceci: -
Supposons maintenant que vous demandez d'anciens enregistrements (chargez plus) et que l'enregistrement "id2" soit mis à jour par quelqu'un et que les enregistrements "id5" et "id8" soient supprimés du serveur, alors la réponse de votre serveur devrait ressembler à ceci: -
Mais dans ce cas, si vous avez beaucoup d'enregistrements locaux mis en cache, supposez 500, alors votre chaîne de requête sera trop longue comme ceci: -
Approche 2: lorsque le serveur est suffisamment intelligent pour gérer les états des objets en fonction de la date.
Vous pouvez envoyer l'identifiant du premier enregistrement et le dernier enregistrement et l'heure de l'époque de la demande précédente. De cette façon, votre demande est toujours petite même si vous avez une grande quantité d'enregistrements mis en cache
Exemple:- Si vous demandez plus de chargement, votre demande devrait ressembler à ceci: -
Votre serveur est responsable de renvoyer les identifiants des enregistrements supprimés qui sont supprimés après le last_request_time ainsi que de renvoyer l'enregistrement mis à jour après last_request_time entre "id1" et "id10".
Tirer pour rafraîchir:-
Charger plus
la source
Il peut être difficile de trouver les meilleures pratiques, car la plupart des systèmes dotés d'API ne conviennent pas à ce scénario, car il s'agit d'un avantage extrême ou ils ne suppriment généralement pas les enregistrements (Facebook, Twitter). Facebook dit en fait que chaque "page" peut ne pas avoir le nombre de résultats demandé en raison du filtrage effectué après la pagination. https://developers.facebook.com/blog/post/478/
Si vous avez vraiment besoin d'accommoder ce boîtier de bord, vous devez vous "souvenir" de l'endroit où vous vous êtes arrêté. La suggestion de jandjorgensen est à peu près exacte, mais j'utiliserais un champ garanti unique comme la clé primaire. Vous devrez peut-être utiliser plusieurs champs.
En suivant le flux de Facebook, vous pouvez (et devez) mettre en cache les pages déjà demandées et simplement renvoyer celles dont les lignes supprimées sont filtrées si elles demandent une page qu'elles avaient déjà demandée.
la source
La pagination est généralement une opération "utilisateur" et pour éviter une surcharge à la fois sur les ordinateurs et le cerveau humain, vous donnez généralement un sous-ensemble. Cependant, plutôt que de penser que nous n’obtenons pas la liste complète, il vaut peut-être mieux demander est-ce important?
Si une vue de défilement en direct précise est nécessaire, les API REST qui sont de nature demande / réponse ne sont pas bien adaptées à cette fin. Pour cela, vous devez envisager les WebSockets ou les événements envoyés par le serveur HTML5 pour informer votre front-end lors du traitement des modifications.
Maintenant, s'il y a un besoin d'obtenir un instantané des données, je fournirais simplement un appel API qui fournit toutes les données en une seule demande sans pagination. Attention, vous auriez besoin de quelque chose qui ferait le streaming de la sortie sans la charger temporairement en mémoire si vous avez un grand ensemble de données.
Pour mon cas, je désigne implicitement certains appels d'API pour permettre d'obtenir toutes les informations (principalement les données de la table de référence). Vous pouvez également sécuriser ces API afin qu'elles n'endommagent pas votre système.
la source
Option A: pagination du jeu de clés avec horodatage
Afin d'éviter les inconvénients de la pagination offset que vous avez mentionnés, vous pouvez utiliser la pagination basée sur le jeu de clés. Habituellement, les entités ont un horodatage qui indique leur heure de création ou de modification. Cet horodatage peut être utilisé pour la pagination: il suffit de passer l'horodatage du dernier élément comme paramètre de requête pour la prochaine requête. Le serveur, à son tour, utilise l'horodatage comme critère de filtre (par exemple
WHERE modificationDate >= receivedTimestampParameter
)De cette façon, vous ne manquerez aucun élément. Cette approche devrait être suffisante pour de nombreux cas d'utilisation. Cependant, gardez à l'esprit les points suivants:
Vous pouvez réduire ces inconvénients en augmentant la taille de la page et en utilisant des horodatages avec une précision en millisecondes.
Option B: Pagination du jeu de clés étendu avec un jeton de continuation
Pour gérer les inconvénients mentionnés de la pagination normale du jeu de clés, vous pouvez ajouter un décalage à l'horodatage et utiliser un soi-disant "jeton de continuation" ou "curseur". Le décalage est la position de l'élément par rapport au premier élément avec le même horodatage. Habituellement, le jeton a un format comme
Timestamp_Offset
. Il est transmis au client dans la réponse et peut être renvoyé au serveur afin de récupérer la page suivante.Le jeton "1512757072_2" pointe vers le dernier élément de la page et indique "le client a déjà obtenu le deuxième élément avec l'horodatage 1512757072". De cette façon, le serveur sait où continuer.
Veuillez noter que vous devez gérer les cas où les éléments ont été modifiés entre deux demandes. Cela se fait généralement en ajoutant une somme de contrôle au jeton. Cette somme de contrôle est calculée sur les ID de tous les éléments avec cet horodatage. Donc , nous nous retrouvons avec un format de jeton comme ceci:
Timestamp_Offset_Checksum
.Pour plus d'informations sur cette approche, consultez l'article de blog " Pagination de l'API Web avec des jetons de continuation ". Un inconvénient de cette approche est la mise en œuvre délicate car il existe de nombreux cas d'angle qui doivent être pris en compte. C'est pourquoi les bibliothèques comme le jeton de continuation peuvent être pratiques (si vous utilisez Java / un langage JVM). Avertissement: je suis l'auteur de l'article et co-auteur de la bibliothèque.
la source
Je pense que votre API répond actuellement comme il se doit. Les 100 premiers enregistrements de la page dans l'ordre global des objets que vous gérez. Votre explication indique que vous utilisez une sorte d'ID de commande pour définir l'ordre de vos objets pour la pagination.
Maintenant, au cas où vous voudriez que la page 2 commence toujours à partir de 101 et se termine à 200, alors vous devez définir le nombre d'entrées sur la page comme variable, car elles sont sujettes à suppression.
Vous devriez faire quelque chose comme le pseudocode ci-dessous:
la source
Juste pour ajouter à cette réponse de Kamilk: https://www.stackoverflow.com/a/13905589
la source
J'y ai longuement réfléchi et j'ai finalement trouvé la solution que je décrirai ci-dessous. C'est une assez grande étape dans la complexité, mais si vous effectuez cette étape, vous vous retrouverez avec ce que vous recherchez vraiment, ce qui est des résultats déterministes pour les demandes futures.
Votre exemple d'élément supprimé n'est que la pointe de l'iceberg. Et si vous filtrez par
color=blue
mais que quelqu'un change les couleurs des éléments entre les demandes? La récupération fiable de tous les éléments de manière paginée est impossible ... à moins que ... nous n'implémentions l'historique des révisions .Je l'ai implémenté et c'est en fait moins difficile que ce à quoi je m'attendais. Voici ce que j'ai fait:
changelogs
avec une colonne d'identification à incrémentation automatiqueid
champ, mais ce n'est pas la clé primairechangeId
champ qui est à la fois la clé primaire et la clé étrangère des journaux des modifications.changelogs
, saisit l'ID et l'affecte à un nouvelle version de l'entité, qu'il insère ensuite dans la base de donnéeschangeId
représente un instantané unique des données sous-jacentes au moment où la modification a été créée.changeId
pour toujours. Les résultats n'expireront jamais car ils ne changeront jamais.la source
Une autre option de pagination dans les API RESTFul consiste à utiliser l'en-tête Link présenté ici . Par exemple, Github l' utilise comme suit:
Les valeurs possibles pour
rel
sont: premier, dernier, suivant, précédent . Mais en utilisant l'en-Link
tête, il peut ne pas être possible de spécifier total_count (nombre total d'éléments).la source