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!

199voto

ramblinjan Punkte 6438

Ich bin nicht ganz sicher, wie Ihre Daten gehandhabt wird, so dass dies kann oder kann nicht funktionieren, aber haben Sie paginating mit einem Zeitstempel-Feld in Betracht gezogen?

Wenn Sie /foos abfragen, erhalten Sie 100 Ergebnisse. Ihre API sollte dann etwa so etwas zurückgeben (unter der Annahme, dass es sich um JSON handelt, aber wenn XML benötigt wird, können die gleichen Grundsätze befolgt werden):

{
    "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"
    }

}

Nur ein Hinweis: Die Verwendung von nur einem Zeitstempel setzt eine implizite "Begrenzung" Ihrer Ergebnisse voraus. Vielleicht möchten Sie eine explizite Grenze hinzufügen oder auch eine until Eigentum.

Der Zeitstempel kann anhand des letzten Datenelements in der Liste dynamisch ermittelt werden. Dies scheint mehr oder weniger die Art und Weise zu sein, wie Facebook die Seiten in seiner Grafik-API (scrollen Sie nach unten, um die Links zur Paginierung in dem oben angegebenen Format zu sehen).

Ein Problem könnte sein, wenn Sie ein Datenelement hinzufügen, aber nach Ihrer Beschreibung klingt es so, als würden sie am Ende hinzugefügt werden (falls nicht, lassen Sie es mich wissen und ich werde sehen, ob ich das verbessern kann).

33voto

kamilk Punkte 3401

Wenn Sie eine Paginierung haben, sortieren Sie die Daten auch nach einem Schlüssel. Warum lassen Sie API-Clients nicht den Schlüssel des letzten Elements der zuvor zurückgegebenen Sammlung in die URL aufnehmen und fügen eine WHERE Klausel zu Ihrer SQL-Abfrage hinzufügen (oder etwas Gleichwertiges, wenn Sie kein SQL verwenden), damit nur die Elemente zurückgegeben werden, deren Schlüssel größer als dieser Wert ist?

29voto

Will Hartung Punkte 110997

Sie haben mehrere Probleme.

Erstens haben Sie das von Ihnen zitierte Beispiel.

Ein ähnliches Problem tritt auch auf, wenn Zeilen eingefügt werden, aber in diesem Fall erhält der Benutzer doppelte Daten (wohl einfacher zu handhaben als fehlende Daten, aber dennoch ein Problem).

Wenn Sie keinen Snapshot des ursprünglichen Datensatzes erstellen, ist dies nur eine Tatsache des Lebens.

Sie können den Benutzer veranlassen, einen expliziten Snapshot zu erstellen:

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

Welche Ergebnisse:

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

Dann können Sie diese Seite den ganzen Tag lang aufrufen, da sie jetzt statisch ist. Dies kann relativ leicht sein, da Sie nur die eigentlichen Dokumentschlüssel und nicht die gesamten Zeilen erfassen können.

Wenn der Anwendungsfall einfach darin besteht, dass Ihre Benutzer alle Daten haben wollen (und brauchen), dann können Sie sie ihnen einfach zur Verfügung stellen:

GET /query/12345?all=true

und schicken Sie einfach das ganze Paket.

21voto

Je nach Ihrer serverseitigen Logik gibt es zwei Ansätze.

Ansatz 1: Wenn der Server nicht intelligent genug ist, um Objektzustände zu verarbeiten.

Sie könnten alle eindeutigen IDs der zwischengespeicherten Datensätze an den Server senden, z. B. ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] und einen booleschen Parameter, um zu wissen, ob Sie neue Datensätze (zum Aktualisieren abrufen) oder alte Datensätze (mehr laden) anfordern.

Ihr Server sollte dafür verantwortlich sein, neue Datensätze (weitere Datensätze laden oder neue Datensätze per Pull-to-Refresh) sowie die IDs gelöschter Datensätze aus ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] zurückzugeben.

Beispiel:- Wenn Sie mehr Last anfordern, sollte Ihre Anfrage in etwa so aussehen:-

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]
}

Nehmen wir an, Sie fordern alte Datensätze an (mehr laden) und nehmen wir an, dass der "id2"-Datensatz von jemandem aktualisiert wird und die "id5"- und "id8"-Datensätze vom Server gelöscht werden, dann sollte die Antwort Ihres Servers etwa so aussehen:-

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

Aber in diesem Fall, wenn Sie eine Menge lokaler Cache-Datensätze haben, die 500 annehmen, dann wird Ihr Anforderungsstring zu lang sein wie dieser:-

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10",………,"id500"]//Too long request
}

Ansatz 2: Wenn der Server intelligent genug ist, um Objektzustände je nach Datum zu behandeln.

Sie können die Kennung des ersten Datensatzes und des letzten Datensatzes sowie die Epochenzeit der vorherigen Anfrage senden. Auf diese Weise ist Ihre Anfrage immer klein, auch wenn Sie eine große Anzahl von Datensätzen im Cache haben.

Beispiel:- Wenn Sie mehr Last anfordern, sollte Ihre Anfrage in etwa so aussehen:-

{
        "isRefresh" : false,
        "firstId" : "id1",
        "lastId" : "id10",
        "last_request_time" : 1421748005
}

Ihr Server ist verantwortlich für die Rückgabe der IDs der gelöschten Datensätze, die nach der last_request_time gelöscht wurden, sowie für die Rückgabe der aktualisierten Datensätze nach der last_request_time zwischen "id1" und "id10".

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

Zum Aktualisieren ziehen:-

enter image description here

Mehr laden

enter image description here

15voto

Brent Baisley Punkte 12551

Es kann schwierig sein, bewährte Verfahren zu finden, da die meisten Systeme mit APIs dieses Szenario nicht berücksichtigen, da es sich um einen extremen Rand handelt, oder sie löschen normalerweise keine Datensätze (Facebook, Twitter). Facebook sagt sogar, dass jede "Seite" aufgrund der Filterung nach der Paginierung möglicherweise nicht die gewünschte Anzahl von Ergebnissen enthält. https://developers.facebook.com/blog/post/478/

Wenn Sie diesen Randfall wirklich berücksichtigen 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.

Wenn man dem Ablauf von Facebook folgt, kann (und sollte) man die bereits angeforderten Seiten zwischenspeichern und nur die Seiten zurückgeben, bei denen die gelöschten Zeilen gefiltert wurden, wenn sie eine Seite anfordern, die sie bereits angefordert hatten.

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