Ich würde gerne Hilfe bei der Bearbeitung eines seltsamen Randfalls mit einer paginierten API erhalten, die ich erstelle.
Wie viele APIs paginiert diese große Ergebnisse. Wenn Sie / foos abfragen, erhalten Sie 100 Ergebnisse (z. B. foo # 1-100) und einen Link zu / foos? Page = 2, der foo # 101-200 zurückgeben sollte.
Wenn foo # 10 aus dem Datensatz gelöscht wird, bevor der API-Konsument die nächste Abfrage durchführt, wird / foos? Page = 2 leider um 100 versetzt und foos # 102-201 zurückgegeben.
Dies ist ein Problem für API-Konsumenten, die versuchen, alle Foos zu ziehen - sie erhalten kein foo # 101.
Was ist die beste Vorgehensweise, um damit umzugehen? Wir möchten es so leicht wie möglich machen (dh vermeiden, Sitzungen für API-Anfragen zu bearbeiten). Beispiele von anderen APIs wären sehr dankbar!
quelle
Antworten:
Ich bin mir nicht ganz sicher, wie Ihre Daten behandelt werden. Dies kann also funktionieren oder auch nicht. Haben Sie darüber nachgedacht, mit einem Zeitstempelfeld zu paginieren?
Wenn Sie / foos abfragen, erhalten Sie 100 Ergebnisse. Ihre API sollte dann ungefähr so zurückgeben (unter der Annahme von JSON, aber wenn XML benötigt wird, können dieselben Prinzipien befolgt werden):
Nur eine Anmerkung, nur die Verwendung eines Zeitstempels hängt von einem impliziten "Limit" in Ihren Ergebnissen ab. Möglicherweise möchten Sie ein explizites Limit hinzufügen oder auch eine
until
Eigenschaft verwenden.Der Zeitstempel kann dynamisch anhand des letzten Datenelements in der Liste ermittelt werden. Dies scheint mehr oder weniger die Art und Weise zu sein, wie Facebook in seiner Graph-API paginiert (scrollen Sie nach unten, um die Paginierungslinks in dem oben angegebenen Format anzuzeigen).
Ein Problem kann sein, wenn Sie ein Datenelement hinzufügen, aber basierend auf Ihrer Beschreibung klingt es so, als würden sie am Ende hinzugefügt (wenn nicht, lassen Sie es mich wissen und ich werde sehen, ob ich dies verbessern kann).
quelle
Sie haben mehrere Probleme.
Zunächst haben Sie das von Ihnen zitierte Beispiel.
Sie haben auch ein ähnliches Problem, wenn Zeilen eingefügt werden. In diesem Fall erhält der Benutzer jedoch doppelte Daten (möglicherweise einfacher zu verwalten als fehlende Daten, aber immer noch ein Problem).
Wenn Sie keinen Schnappschuss des Originaldatensatzes machen, ist dies nur eine Tatsache des Lebens.
Sie können den Benutzer einen expliziten Schnappschuss erstellen lassen:
Welche Ergebnisse:
Dann können Sie das den ganzen Tag lang durchblättern, da es jetzt statisch ist. Dies kann relativ leicht sein, da Sie nur die tatsächlichen Dokumentschlüssel und nicht die gesamten Zeilen erfassen können.
Wenn der Anwendungsfall einfach darin besteht, dass Ihre Benutzer alle Daten wollen (und brauchen), können Sie sie ihnen einfach geben:
und senden Sie einfach das gesamte Kit.
quelle
Wenn Sie eine Paginierung haben, sortieren Sie die Daten auch nach einem Schlüssel. Lassen Sie API-Clients den Schlüssel des letzten Elements der zuvor zurückgegebenen Auflistung in die URL aufnehmen und fügen Sie
WHERE
Ihrer SQL-Abfrage eine Klausel hinzu (oder eine entsprechende Klausel, wenn Sie kein SQL verwenden), sodass nur die Elemente zurückgegeben werden, für die ist der Schlüssel größer als dieser Wert?quelle
Abhängig von Ihrer serverseitigen Logik kann es zwei Ansätze geben.
Ansatz 1: Wenn der Server nicht intelligent genug ist, um Objektzustände zu verarbeiten.
Sie können alle zwischengespeicherten eindeutigen IDs des Datensatzes an den Server senden, z. B. ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] und ein boolescher Parameter, um festzustellen, ob Sie neue Datensätze anfordern (zum Aktualisieren ziehen) oder alte Datensätze (mehr laden).
Ihr Server sollte dafür verantwortlich sein, neue Datensätze (mehr Datensätze oder neue Datensätze per Pull zum Aktualisieren laden) sowie IDs gelöschter Datensätze aus ["id1", "id2", "id3", "id4", "id5", "zurückzugeben. id6 "," id7 "," id8 "," id9 "," id10 "].
Beispiel: - Wenn Sie mehr laden möchten, sollte Ihre Anfrage ungefähr so aussehen: -
Angenommen, Sie fordern alte Datensätze an (mehr laden) und nehmen an, dass der Datensatz "id2" von jemandem aktualisiert wird und die Datensätze "id5" und "id8" vom Server gelöscht werden. Dann sollte Ihre Serverantwort ungefähr so aussehen: -
In diesem Fall ist Ihre Anforderungszeichenfolge jedoch zu lang, wenn Sie viele lokal zwischengespeicherte Datensätze mit 500 angenommen haben: -
Ansatz 2: Wenn der Server intelligent genug ist, um Objektzustände nach Datum zu verarbeiten.
Sie können die ID des ersten Datensatzes und des letzten Datensatzes sowie die Epoche der vorherigen Anforderung senden. Auf diese Weise ist Ihre Anfrage immer klein, selbst wenn Sie eine große Anzahl zwischengespeicherter Datensätze haben
Beispiel: - Wenn Sie mehr laden möchten, sollte Ihre Anfrage ungefähr so aussehen: -
Ihr Server ist dafür verantwortlich, die IDs der gelöschten Datensätze zurückzugeben, die nach der letzten_Anforderungszeit gelöscht werden, sowie den aktualisierten Datensatz nach der letzten_Anforderungszeit zwischen "ID1" und "ID10" zurückzugeben.
Zum Aktualisieren ziehen: -
Mehr laden
quelle
Es kann schwierig sein, Best Practices zu finden, da die meisten Systeme mit APIs dieses Szenario nicht berücksichtigen, da dies ein extremer Vorteil ist oder sie normalerweise keine Datensätze löschen (Facebook, Twitter). Facebook sagt tatsächlich, dass auf jeder "Seite" möglicherweise nicht die Anzahl der angeforderten Ergebnisse angezeigt wird, da nach der Paginierung gefiltert wurde. https://developers.facebook.com/blog/post/478/
Wenn Sie diesen Randfall wirklich aufnehmen müssen, müssen Sie sich "merken", wo Sie aufgehört haben. Der Vorschlag von jandjorgensen ist genau richtig, aber ich würde ein Feld verwenden, das garantiert eindeutig ist, wie der Primärschlüssel. Möglicherweise müssen Sie mehr als ein Feld verwenden.
Nach dem Ablauf von Facebook können (und sollten) Sie die bereits angeforderten Seiten zwischenspeichern und nur diejenigen mit gelöschten Zeilen zurückgeben, die gefiltert wurden, wenn sie eine bereits angeforderte Seite anfordern.
quelle
Paginierung ist im Allgemeinen eine "Benutzer" -Operation. Um eine Überlastung sowohl des Computers als auch des menschlichen Gehirns zu verhindern, geben Sie im Allgemeinen eine Teilmenge an. Anstatt jedoch zu denken, dass wir nicht die gesamte Liste erhalten, ist es vielleicht besser zu fragen, ob es wichtig ist.
Wenn eine genaue Live-Bildlaufansicht erforderlich ist, sind REST-APIs, die Anforderungs- / Antwortansichten sind, für diesen Zweck nicht gut geeignet. Zu diesem Zweck sollten Sie WebSockets oder vom HTML5-Server gesendete Ereignisse berücksichtigen, um Ihr Front-End über Änderungen zu informieren.
Nun, wenn es nötig ist eine Momentaufnahme der Daten , würde ich nur einen API-Aufruf bereitstellen, der alle Daten in einer Anforderung ohne Paginierung bereitstellt. Wohlgemerkt, Sie benötigen etwas, das das Streaming der Ausgabe ermöglicht, ohne sie vorübergehend in den Speicher zu laden, wenn Sie über einen großen Datensatz verfügen.
In meinem Fall bezeichne ich implizit einige API-Aufrufe, um das Abrufen der gesamten Informationen (hauptsächlich Referenztabellendaten) zu ermöglichen. Sie können diese APIs auch sichern, damit Ihr System nicht beschädigt wird.
quelle
Option A: Keyset-Paginierung mit einem Zeitstempel
Um die von Ihnen erwähnten Nachteile der Offset-Paginierung zu vermeiden, können Sie eine Keyset-basierte Paginierung verwenden. Normalerweise haben die Entitäten einen Zeitstempel, der ihre Erstellungs- oder Änderungszeit angibt. Dieser Zeitstempel kann für die Paginierung verwendet werden: Übergeben Sie einfach den Zeitstempel des letzten Elements als Abfrageparameter für die nächste Anforderung. Der Server, der wiederum verwendet den Zeitstempel als Filterkriterium (z
WHERE modificationDate >= receivedTimestampParameter
)Auf diese Weise verpassen Sie kein Element. Dieser Ansatz sollte für viele Anwendungsfälle gut genug sein. Beachten Sie jedoch Folgendes:
Sie können diese Nachteile weniger wahrscheinlich machen, indem Sie die Seitengröße erhöhen und Zeitstempel mit Millisekundengenauigkeit verwenden.
Option B: Erweiterte Keyset-Paginierung mit einem Fortsetzungstoken
Um die genannten Nachteile der normalen Keyset-Paginierung zu beheben, können Sie dem Zeitstempel einen Versatz hinzufügen und ein sogenanntes "Continuation Token" oder "Cursor" verwenden. Der Versatz ist die Position des Elements relativ zum ersten Element mit demselben Zeitstempel. Normalerweise hat das Token ein Format wie
Timestamp_Offset
. Es wird in der Antwort an den Client übergeben und kann an den Server zurückgesendet werden, um die nächste Seite abzurufen.Das Token "1512757072_2" zeigt auf das letzte Element der Seite und gibt an, dass der Client bereits das zweite Element mit dem Zeitstempel 1512757072 erhalten hat. Auf diese Weise weiß der Server, wo er fortfahren soll.
Bitte beachten Sie, dass Sie Fälle behandeln müssen, in denen die Elemente zwischen zwei Anforderungen geändert wurden. Dies erfolgt normalerweise durch Hinzufügen einer Prüfsumme zum Token. Diese Prüfsumme wird über die IDs aller Elemente mit diesem Zeitstempel berechnet. Wir haben also ein Token-Format wie das folgende:
Timestamp_Offset_Checksum
.Weitere Informationen zu diesem Ansatz finden Sie im Blog-Beitrag " Web-API-Paginierung mit Fortsetzungstoken ". Ein Nachteil dieses Ansatzes ist die schwierige Implementierung, da viele Eckfälle berücksichtigt werden müssen. Aus diesem Grund können Bibliotheken wie Continuation-Token nützlich sein (wenn Sie Java / eine JVM-Sprache verwenden). Haftungsausschluss: Ich bin der Autor des Beitrags und Mitautor der Bibliothek.
quelle
Ich denke, derzeit reagiert Ihre API tatsächlich so, wie es sollte. Die ersten 100 Datensätze auf der Seite in der Gesamtreihenfolge der Objekte, die Sie verwalten. Ihre Erklärung besagt, dass Sie eine Art von Bestell-IDs verwenden, um die Reihenfolge Ihrer Objekte für die Paginierung zu definieren.
Wenn Sie möchten, dass Seite 2 immer bei 101 beginnt und bei 200 endet, müssen Sie die Anzahl der Einträge auf der Seite als variabel festlegen, da sie gelöscht werden müssen.
Sie sollten so etwas wie den folgenden Pseudocode tun:
quelle
Nur um diese Antwort von Kamilk zu ergänzen: https://www.stackoverflow.com/a/13905589
quelle
Ich habe lange und gründlich darüber nachgedacht und schließlich die Lösung gefunden, die ich unten beschreiben werde. Es ist ein ziemlich großer Schritt in der Komplexität, aber wenn Sie diesen Schritt machen, erhalten Sie das, wonach Sie wirklich suchen, was deterministische Ergebnisse für zukünftige Anforderungen sind.
Ihr Beispiel für das Löschen eines Elements ist nur die Spitze des Eisbergs. Was ist, wenn Sie filtern,
color=blue
aber jemand die Artikelfarben zwischen den Anforderungen ändert? Es ist unmöglich, alle Elemente auf ausgelagerte Weise zuverlässig abzurufen ... es sei denn ... wir implementieren den Revisionsverlauf .Ich habe es implementiert und es ist weniger schwierig als ich erwartet hatte. Folgendes habe ich getan:
changelogs
mit einer ID-Spalte mit automatischer Inkrementierung erstelltid
Feld, aber dies ist nicht der PrimärschlüsselchangeId
Feld, das sowohl der Primärschlüssel als auch ein Fremdschlüssel für Änderungsprotokolle ist.changelogs
, greift auf die ID zu und weist sie einer neuen Version der Entität zu, die es dann in die Datenbank einfügtchangeId
eine eindeutige Momentaufnahme der zugrunde liegenden Daten zum Zeitpunkt der Erstellung der Änderung dar.changeId
, für immer zwischenspeichern können. Die Ergebnisse werden niemals verfallen, weil sie sich niemals ändern werden.quelle
Eine weitere Option für die Paginierung in RESTFul-APIs ist die Verwendung des hier eingeführten Link-Headers . Zum Beispiel verwendet Github es wie folgt:
Die möglichen Werte für
rel
sind: first, last, next, previous . Bei Verwendung desLink
Headers ist es jedoch möglicherweise nicht möglich, total_count (Gesamtzahl der Elemente) anzugeben .quelle