354 Stimmen

Bewährte API-Paginierungsverfahren

Ich würde gerne etwas Hilfe bei der Behandlung eines seltsamen Randfalls mit einer paginierten API, die ich gerade aufbaue.

Wie viele APIs paginiert auch diese große Ergebnisse. Wenn Sie /foos abfragen, erhalten Sie 100 Ergebnisse (d. h. foo #1-100) und einen Link zu /foos?page=2, der foo #101-200 liefern sollte.

Wenn jedoch foo #10 aus dem Datensatz gelöscht wird, bevor der API-Konsument die nächste Abfrage stellt, wird /foos?page=2 um 100 versetzt und foos #102-201 zurückgegeben.

Dies ist ein Problem für API-Konsumenten, die versuchen, alle foos zu ziehen - sie werden foo #101 nicht erhalten.

Wie ist das am besten zu handhaben? Wir würden es gerne so einfach wie möglich machen (d.h. keine Sitzungen für API-Anfragen). Beispiele aus anderen APIs wären uns sehr willkommen!

3voto

Stijn de Witt Punkte 35759

Ich habe lange darüber nachgedacht und bin schließlich zu der Lösung gekommen, die ich im Folgenden beschreiben werde. Es ist ein ziemlich großer Schritt nach oben in der Komplexität, aber wenn Sie diesen Schritt machen, werden Sie am Ende mit dem, was Sie wirklich wollen, die deterministische Ergebnisse für zukünftige Anfragen ist.

Ihr Beispiel eines gelöschten Artikels ist nur die Spitze des Eisbergs. Was ist, wenn Sie filtern nach color=blue aber jemand ändert die Farben der Gegenstände zwischen den Anfragen? Das zuverlässige Abrufen aller Elemente in einer paginierten Weise ist unmöglich ... es sei denn ... wir setzen Revisionsgeschichte .

Ich habe es umgesetzt, und es ist tatsächlich weniger schwierig als erwartet. Hier ist, was ich tat:

  • Ich habe eine einzelne Tabelle erstellt changelogs mit einer automatisch inkrementierenden ID-Spalte
  • Meine Entitäten haben eine id Feld, aber dies ist nicht der Primärschlüssel
  • Die Entitäten haben eine changeId das sowohl der Primärschlüssel als auch ein Fremdschlüssel für Änderungsprotokolle ist.
  • Immer wenn ein Benutzer einen Datensatz erstellt, aktualisiert oder löscht, fügt das System einen neuen Datensatz in changelogs die id und ordnet sie einem neu Version der Entität, die sie dann in die DB einfügt
  • Meine Abfragen wählen die maximale changeId (gruppiert nach id) aus und verknüpfen diese, um die neuesten Versionen aller Datensätze zu erhalten.
  • Filter werden auf die neuesten Datensätze angewendet
  • Ein Statusfeld hält fest, ob ein Element gelöscht ist
  • Die max changeId wird an den Client zurückgegeben und als Abfrageparameter in nachfolgenden Anfragen hinzugefügt
  • Da nur neue Änderungen erstellt werden, ist jede einzelne changeId stellt einen eindeutigen Schnappschuss der zugrunde liegenden Daten zum Zeitpunkt der Erstellung der Änderung dar.
  • Das bedeutet, dass Sie die Ergebnisse von Anfragen, die den Parameter changeId in ihnen für immer. Die Ergebnisse werden nie verfallen, weil sie sich nie ändern werden.
  • Dies eröffnet auch interessante Funktionen wie Rollback/Revert, Synchronisierung des Client-Cache usw. Alle Funktionen, die von der Änderungshistorie profitieren.

2voto

zangw Punkte 36978

Siehe API-Paginierung Design könnten wir die Paginierung api durch Cursor

Sie haben ein Konzept, das als Cursor bezeichnet wird - es ist ein Zeiger auf eine Zeile. Sie können also zu einer Datenbank sagen: "Gib mir 100 Zeilen nach dieser Zeile zurück". Und das ist für eine Datenbank viel einfacher, da die Wahrscheinlichkeit groß ist, dass Sie die Zeile über ein Feld mit einem Index identifizieren können. Und plötzlich brauchen Sie diese Zeilen nicht mehr zu holen und zu überspringen, sondern Sie gehen direkt an ihnen vorbei. Ein Beispiel:

  GET /api/products
  {"items": [...100 products],
   "cursor": "qWe"}

API eine (undurchsichtige) Zeichenkette zurück, die Sie dann zum Abrufen der nächsten Seite verwenden können:

GET /api/products?cursor=qWe
{"items": [...100 products],
 "cursor": "qWr"}

Für die Umsetzung gibt es viele Möglichkeiten. Im Allgemeinen haben Sie einige Bestellkriterien, zum Beispiel die Produkt-ID. In diesem Fall kodieren Sie Ihre Produkt-ID mit einem reversiblen Algorithmus (z. B. hashids ). Wenn Sie eine Anfrage mit dem Cursor erhalten, entschlüsseln Sie diese und erstellen eine Abfrage wie WHERE id > :cursor LIMIT 100 .

Vorteil:

  • Die Abfrageleistung von db könnte verbessert werden durch cursor
  • Gute Handhabung, wenn bei der Abfrage neue Inhalte in die Datenbank eingefügt wurden

Nachteil:

  • Es ist unmöglich, eine previous page Verknüpfung mit einer zustandslosen API

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X