La pagination est un contrat de cohérence, pas un détail de réponse
Catalogues, commandes, clients et abonnements évoluent pendant que les applications, connecteurs et outils internes les parcourent. Une API qui renvoie tout en une réponse gaspille la mémoire, augmente la latence et fragilise les intégrations. Découper le résultat en pages maîtrise la taille, mais pose une question plus difficile : que signifie suivant lorsque prix, états et enregistrements changent ? Sans réponse contractuelle, un client peut recevoir deux fois la même commande, en omettre une autre ou terminer une synchronisation avec un ensemble incohérent.
La solution ne consiste pas à remplacer offset par une chaîne arbitraire appelée curseur. Le service a besoin d'un ordre total, d'un jeton lié à la requête, de règles explicites sur les modifications concurrentes et de métriques permettant de distinguer les erreurs du client de l’instabilité des données. Définissez d'abord l'usage : navigation, export complet, synchronisation incrémentale et recherche profonde n'exigent pas les mêmes garanties. Ce cadrage évite de promettre un instantané lorsque le stockage n'offre qu'une progression monotone.
Définir le contrat avant l'implémentation
Séparer taille, position et filtres
Un contrat clair expose une taille demandée, un jeton reçu dans la réponse précédente et un éventuel jeton suivant. Google AIP-158 décrit des champs tels que page_size, page_token et next_page_token : le client répète la requête en changeant uniquement le jeton. Fixez un maximum côté serveur et le comportement des valeurs absentes ou excessives. La taille demandée ne garantit pas le nombre rendu, car autorisation, filtres et disponibilité peuvent produire une page plus courte.
Le jeton représente la position avec les paramètres qui définissent l'ensemble : filtres, tri, locataire et version du contrat. Si le client modifie un de ces éléments, refusez le jeton avec une erreur stable au lieu de poursuivre une autre séquence. Ne faites pas confiance à des champs modifiables envoyés séparément. Pour une synchronisation, documentez aussi l'expiration, la possibilité de rejouer le jeton et l'opération qui démarre une nouvelle lecture.
Conserver un curseur opaque et versionnable
Un curseur n'est pas une clé primaire publique. Encodez dans une structure interne la clé de tri, le critère de départage, la version et, si nécessaire, une référence de session. Protégez-la contre la manipulation avec une signature ou un identifiant côté serveur. L'encodage n'est pas du chiffrement : n'incluez ni donnée personnelle, ni secret, ni détail confidentiel. L'opacité permet de faire évoluer le format sans que les clients l'interprètent, dans une fenêtre de compatibilité annoncée.
Construire un ordre total et reproductible
Ajouter un critère de départage unique
Trier seulement par date de création ne suffit pas, car plusieurs éléments peuvent partager le même instant. Utilisez une séquence totale comme created_at DESC, id DESC et placez les deux valeurs dans le curseur. La page suivante applique un prédicat lexicographique conforme à la direction au lieu de recompter les lignes précédentes. Si l'utilisateur choisit le tri, chaque option autorisée demande un départage stable et un index adapté. Les tris arbitraires non soutenus par le stockage deviennent imprévisibles.
PostgreSQL rappelle que LIMIT et OFFSET doivent être associés à un ORDER BY produisant un ordre unique pour obtenir un sous-ensemble prévisible. Les lignes ignorées par l'offset doivent tout de même être calculées. L'offset convient à de petits écrans d'administration ou à des pages numérotées peu profondes, mais mal aux grands exports. La pagination keyset reprend après la dernière clé observée et maintient un travail proche de la taille de page lorsque requête et index sont alignés.
Aligner la requête et l'index
Examinez les plans pour les filtres et directions réellement employés. L'index doit servir les colonnes du locataire, les prédicats sélectifs et le tuple de tri sans multiplier les structures pour chaque variante théorique. Mesurez durée, lignes lues et mémoire de la première page jusqu'à une page profonde. Demander un élément de plus que la limite permet de savoir si une suite existe, ou utilisez un signal équivalent, sans calculer un total coûteux inutile au produit.
S'inspirer des contrats publics sans les copier aveuglément
Stripe liste les abonnements avec des paramètres comme starting_after et ending_before, une taille de page maximale et le signal has_more. Un objet déjà reçu devient ainsi le point de progression. Les connexions GraphQL de Shopify exposent pageInfo, hasNextPage et endCursor, puis l'appelant renvoie le curseur dans after. Dans les deux cas, le consommateur suit un jeton du serveur au lieu de dériver un numéro de page.
Ces exemples ne définissent pas votre niveau de cohérence. Un catalogue au classement variable, une liste de commandes et un historique d'événements demandent des politiques différentes. Documentez direction, ordre par défaut, taille maximale, durée de vie et traitement des insertions, mises à jour et suppressions. Si la navigation bidirectionnelle est nécessaire, testez les parcours aller et retour comme deux contrats complets plutôt que comme l'inversion d'une chaîne.
Gérer les écritures concurrentes et la recherche profonde
Choisir une frontière monotone ou un instantané
Avec des clés de tri immuables, un curseur peut garantir que chaque page avance au-delà de la dernière position observée. Les nouveaux éléments placés avant cette frontière n'apparaissent pas dans le parcours courant ; les mises à jour de champs hors tri ne déplacent pas la position. Si la clé de tri change, l'élément peut bouger. Évitez donc les champs modifiables pour les synchronisations ou introduisez un repère immuable. Pour un audit, préférez un instantané ou un journal d'événements à une promesse forte sur une table mouvante.
La recherche ajoute classement et répartition entre shards. Elastic documente search_after avec les mêmes valeurs de requête et de tri et recommande un point in time pour préserver l'état de l'index durant le parcours ; le PIT ajoute un départage interne. Le jeton applicatif peut protéger la référence PIT et les valeurs de tri. Gérez expiration et fermeture, car un instantané prolongé consomme des ressources. À l'expiration, exigez un redémarrage au lieu de simuler une continuité.
Concevoir erreurs, sécurité et observabilité
Distinguez jeton malformé, signature invalide, version non prise en charge, session expirée et paramètres incompatibles. Chaque cas produit une réponse documentée sans dévoiler la structure interne. Limitez taille de page, fréquence et nombre de sessions actives par identité. Un curseur valide n'autorise jamais des données que l'appelant n'a plus le droit de voir. Réappliquez autorisation et périmètre à chaque requête et liez le jeton au bon locataire.
Mesurez pages par parcours, latence selon la profondeur, lignes examinées, jetons refusés, sessions expirées, redémarrages et pages vides offrant encore une suite. Dans les tests, créez des éléments partageant la même date, insérez et supprimez entre deux pages, puis modifiez un champ triable. Vérifiez absence de doublons, absence d'omissions par rapport à la garantie annoncée et terminaison certaine. Les journaux conservent version et empreinte du curseur, jamais le jeton complet ni les données client.
Déployer la pagination sans casser les intégrations
Introduisez le nouveau contrat en parallèle ou dans une version compatible. Observez d'abord les coûts et la justesse en mode miroir, puis migrez un client contrôlé et comparez cardinalité finale et durée à l'ancien chemin. Gardez un retour arrière et une date de fin pour le mode historique. Publiez un exemple de parcours complet et précisez que le jeton est opaque. La qualité se juge dans les pages profondes et pendant les écritures, pas uniquement sur la première réponse.
Une pagination fiable réunit contrat d'API, base de données, recherche, sécurité et télémétrie. Si les catalogues ou connecteurs présentent doublons, délais ou synchronisations incomplètes, les services e-commerce et systèmes d'AE Digital Agency peuvent transformer le flux en protocole mesurable avec curseurs versionnés, index cohérents et déploiement vérifiable.
Questions fréquentes
Quelle différence entre pagination par offset et par curseur ?
L'offset compte et ignore des lignes ; le curseur reprend à une position ordonnée déjà observée. Le second convient mieux aux grandes séquences mouvantes avec un ordre total.
Un curseur garantit-il automatiquement l'absence de doublons ?
Non. Il faut un ordre déterministe, un départage, des règles sur les modifications concurrentes et des requêtes cohérentes. L'opacité seule ne change que la syntaxe.
Quand une API e-commerce a-t-elle besoin d'un instantané ?
Lorsqu'un parcours complet doit refléter une vue cohérente, par exemple pour un audit. Une frontière monotone documentée peut suffire à un flux ordinaire.
Comment traiter un jeton de page expiré ?
Renvoyez une erreur spécifique et demandez de recommencer le parcours. Ne reconstruisez pas silencieusement une autre position et n'acceptez pas des paramètres incompatibles.
Articles connexes
Versionnement des API e-commerce : rétrocompatibilité, dépréciation et migration
Une méthode opérationnelle pour faire évoluer les API de commandes, catalogue, paiement et livraison sans surprendre les intégrations, avec contrats, télémétrie et migration contrôlée.
GraphQL e-commerce sécurisé : coût des requêtes, limites et opérations persistées
Un modèle opérationnel pour protéger le catalogue et le passage en caisse des requêtes GraphQL coûteuses grâce à des budgets mesurables, des limites contextuelles et des opérations persistées.
Limitation de débit des API e-commerce : protéger la connexion et le checkout sans bloquer les clients
Un modèle opérationnel pour différencier connexion, catalogue, panier, checkout et intégrations, avec des réponses 429 exploitables et des métriques de faux positifs.
