Paginierung in einer REST-Webanwendung

329

Dies ist eine allgemeinere Neuformulierung dieser Frage (unter Eliminierung der Rails-spezifischen Teile).

Ich bin nicht sicher, wie ich die Paginierung einer Ressource in einer RESTful-Webanwendung implementieren soll. Angenommen, ich habe eine Ressource namens products, welche der folgenden Methoden ist Ihrer Meinung nach der beste und warum:

1. Verwenden Sie nur Abfragezeichenfolgen

z.B. http://application/products?page=2&sort_by=date&sort_how=asc
Das Problem hierbei ist, dass ich kein Ganzseiten-Caching verwenden kann und auch die URL nicht sehr sauber und leicht zu merken ist.

2. Verwenden von Seiten als Ressourcen und Abfragezeichenfolgen zum Sortieren

z.B. http://application/products/page/2?sort_by=date&sort_how=asc
In diesem Fall besteht das Problem darin, dass http://application/products/pages/1es sich nicht um eine eindeutige Ressource handelt, da die Verwendung zu sort_by=priceeinem völlig anderen Ergebnis führen kann und ich das Seiten-Caching immer noch nicht verwenden kann.

3. Verwenden von Seiten als Ressourcen und eines URL-Segments zum Sortieren

z.B. http://application/products/by-date/page/2
Ich persönlich sehe kein Problem bei der Verwendung dieser Methode, aber jemand hat mich gewarnt, dass dies kein guter Weg ist (er hat keinen Grund angegeben. Wenn Sie also wissen, warum dies nicht empfohlen wird, lassen Sie es mich bitte wissen.)

Irgendwelche Vorschläge, Meinungen, Kritiken sind mehr als willkommen. Vielen Dank.

und ich
quelle
34
Das ist eine gute Frage.
Iain Holder
7
Bonusfrage: Wie geben die Leute normalerweise Seitengrößen an?
Heiko Rupp
Vergessen Sie nicht die Matrix-Parameter w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Antworten:

66

Ich denke, das Problem mit Version 3 ist eher ein "Standpunkt" -Problem - sehen Sie die Seite als Ressource oder als Produkte auf der Seite?

Wenn Sie die Seite als Ressource sehen, ist dies eine perfekte Lösung, da die Abfrage für Seite 2 immer Seite 2 ergibt.

Wenn Sie jedoch die Produkte auf der Seite als Ressource sehen, besteht das Problem, dass sich die Produkte auf Seite 2 ändern (alte Produkte gelöscht oder was auch immer). In diesem Fall gibt der URI nicht immer dieselben Ressourcen zurück.

Beispiel: Ein Kunde speichert einen Link zur Produktlistenseite X. Beim nächsten Öffnen des Links befindet sich das betreffende Produkt möglicherweise nicht mehr auf Seite X.

Fionn
quelle
6
Nun, aber wenn Sie etwas löschen, sollte sich auf derselben URI nichts anderes befinden. Wenn Sie alle Produkte von Seite X löschen - Seite X ist möglicherweise noch gültig, enthält jedoch jetzt die Produkte von Seite X + 1. Der URI für Seite X wurde zum URI für Seite X + 1, wenn Sie ihn in der Ansicht "Produktressourcen" sehen ".
Fionn
1
> Wenn Sie die Seite als Ressource betrachten, ist dies eine perfekte Lösung, da die Abfrage für Seite 2 immer Seite 2 ergibt. Ist dies überhaupt sinnvoll? Dieselbe URL (jede URL, die Seite 2 erwähnt) ergibt immer Seite 2, unabhängig davon, was Sie als Ressource verwenden.
Temoto
2
Wenn Sie eine Seite als Ressource sehen, sollte POST / foo / page wahrscheinlich eingeführt werden, um eine neue Seite zu erstellen, oder?
Temoto
18
Ihre Antwort lautet reibungslos "Richtige Lösung ist 1", gibt sie jedoch nicht an.
Temoto
2
In meinen Augen ist page ein schwebendes Konzept und nicht mit der zugrunde liegenden Domäne verbunden. Und sollte daher nicht als Ressource betrachtet werden. Ich meine schweben in dem Sinne, dass es fließend ist, dass sich das Konzept der Seite mit dem Kontext ändert; Ein Benutzer Ihrer API kann eine mobile App sein, die nur 2 Produkte pro Seite verbrauchen kann, während der andere eine Maschinen-App ist, die die gesamte verdammte Liste verbrauchen kann. Kurz gesagt, die Seite ist eine "Darstellung" der zugrunde liegenden Domänenentität (Produkt) und sollte nicht als Teil der URL enthalten sein. nur als Abfrageparameter.
Kingz
106

Ich stimme Fionn zu, aber ich werde noch einen Schritt weiter gehen und sagen, dass die Seite für mich keine Ressource ist, sondern eine Eigenschaft der Anfrage. Aus diesem Grund habe ich nur die Abfragezeichenfolge für Option 1 ausgewählt. Es fühlt sich einfach richtig an. Mir gefällt sehr, wie die Twitter-API restauriert ist. Nicht zu einfach, nicht zu kompliziert, gut dokumentiert. Ob gut oder schlecht, es ist mein "Gehe zu" -Design, wenn ich auf dem Zaun bin, um etwas in die eine oder andere Richtung zu tun.

slf
quelle
28
+1: Abfragezeichenfolgen sind keine erstklassigen Ressourcenkennungen. Sie dienen lediglich der Klarstellung für die Bestellung und Gruppierung der Ressource.
S.Lott
1
@ S.Lott Die Anfrage ist die Ressource. Was Sie als "erstklassige Ressourcen" bezeichnen , wird von Fielding in Abschnitt 5.2.1.1 seiner Dissertation als Werte definiert . Darüber hinaus gibt Fielding im selben Abschnitt die neueste Version einer Quellcodedatei als Beispiel für eine Ressource an. Wie kann das eine Ressource sein, aber die neuesten 10 Produkte sind "Eigenschaften der Anforderung auf der Produktressource"? Ich verstehe, dass Ihre Ansicht praktischer ist, aber ich denke, dass sie weniger ruhend ist.
Edsioufi
Beachten Sie, dass mein Kommentar nicht bedeutet, dass ich mit der Wahl der Verwendung von Abfragezeichenfolgen über URLs nicht einverstanden bin: Beide sind praktikable Lösungen, solange die API hypermediengesteuert ist, wie @RichApodaca in seiner Antwort erwähnt hat. Ich möchte nur darauf hinweisen, dass die Seite aus REST-Sicht als Ressource betrachtet werden sollte.
Edsioufi
37

HTTP hat einen großartigen Range-Header, der auch für die Paginierung geeignet ist. Sie können senden

Range: pages=1

nur die erste Seite haben. Das kann Sie dazu zwingen, eine Seite zu überdenken. Vielleicht möchte der Kunde eine andere Auswahl an Artikeln. Der Bereichskopf deklariert auch eine Bestellung:

Range: products-by-date=2009_03_27-

um alle Produkte neuer als dieses Datum zu bekommen oder

Range: products-by-date=0-2009_11_30

um alle Produkte älter als dieses Datum zu bekommen. '0' ist wahrscheinlich nicht die beste Lösung, aber RFC scheint etwas für den Bereichsstart zu wollen. Möglicherweise werden HTTP-Parser bereitgestellt, die die Einheiten = -range_end nicht analysieren.

Wenn Header keine (akzeptable) Option sind, denke ich, dass die erste Lösung (alle in Abfragezeichenfolge) eine Möglichkeit ist, mit Seiten umzugehen. Normalisieren Sie jedoch die Abfragezeichenfolgen (sortieren Sie (Schlüssel = Wert) Paare in alphabetischer Reihenfolge). Dies löst das Differenzierungsproblem "? A = 1 & b = x" und "? B = x & a = 1".

temoto
quelle
34
Header sehen auf den ersten Blick vielleicht gut aus, verbieten jedoch das Teilen der Seite (z. B. durch Kopieren der URL). Für Ajax-Anfragen sind sie möglicherweise eine gute Lösung (da von Ajax geänderte Seiten in ihrem aktuellen Status ohnehin nicht freigegeben werden können), aber ich würde sie nicht für die reguläre Paginierung verwenden.
Markus
3
Der Range-Header gilt nur für Bytebereiche. Siehe [die HTTP-Header-Spezifikation] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), Abschnitt 14.35 .
Chris Westin
16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 verwendet Bereichseinheiten in den Headerfeldern Range (Abschnitt 14.35) und Content-Range (Abschnitt 14.16). range-unit = bytes-unit | other-range-unit Vielleicht beziehen Sie sich auf The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.Das ist nicht dasselbe wie Ihre Aussage.
Temoto
1
@ Markus Ich kann mir den Anwendungsfall nicht vorstellen, wenn Sie Rest-API-Ressource teilen :)
JakubKnejzlik
@JakubKnejzlik Die Freigabe ist kein Problem, aber die Verwendung von HTTP-Headern für das Paging verhindert die Verwendung von HATEOAS-Links für das Paging.
Xarx
25

Option 1 scheint insofern am besten zu sein, als Ihre Anwendung die Paginierung als eine Technik zum Erstellen einer anderen Ansicht derselben Ressource betrachtet.

Allerdings ist das URL-Schema relativ unbedeutend. Wenn Sie Ihre Anwendung so gestalten, dass sie hypertextgesteuert ist (da alle REST-Anwendungen per Definition sein müssen), erstellt Ihr Client keine eigenen URIs. Stattdessen gibt Ihre Anwendung die Links zum Client an und der Client folgt ihnen.

Eine Art von Link, den Ihr Kunde bereitstellen kann, ist ein Paginierungslink.

Der angenehme Nebeneffekt all dessen ist, dass Ihre Kunden, selbst wenn Sie Ihre Meinung zur Paginierungs-URI-Struktur ändern und nächste Woche etwas völlig anderes implementieren, ohne Änderungen weiterarbeiten können.

Reiches Apodaca
quelle
3
Schöne Erinnerung an die Verwendung von Hypermedia-ähnlichen Links in REST-Webdiensten.
Paul D. Eden
11

Ich habe immer den Stil von Option 1 verwendet. Das Zwischenspeichern war kein Problem, da sich die Daten in meinem Fall ohnehin häufig ändern. Wenn Sie zulassen, dass die Größe der Seite konfiguriert werden kann, können die Daten erneut nicht zwischengespeichert werden.

Ich finde die URL nicht schwer zu merken oder unrein. Für mich ist dies eine gute Verwendung von Abfrageparametern. Die Ressource ist eindeutig eine Liste von Produkten, und die Abfrageparameter geben nur an, wie die Liste angezeigt werden soll - sortiert und auf welcher Seite.

John Snyders
quelle
1
+1 Ich denke du hast recht und ich werde mit den Abfrageparametern (Option 1) gehen
andi
"Ich finde die URL nicht schwer zu merken". Diese Beobachtung ist in REST-Anwendungen nutzlos, da diese normalerweise nur ein einziges Lesezeichen haben sollten ... Wenn ein Benutzer (oder eine Client-App) versucht, sich die URL zu "merken", ist dies ein gutes Zeichen dafür, dass die API nicht wiederhergestellt ist.
Edsioufi
8

Seltsam, dass niemand darauf hingewiesen hat, dass Option 3 Parameter in einer bestimmten Reihenfolge hat. http // application / products / Date / Descending / Name / Ascending / page / 2 und http // application / products / Name / Ascending / Date / Descending / page / 2

verweisen auf dieselbe Ressource, haben jedoch völlig unterschiedliche URLs.

Für mich scheint Option 1 die akzeptabelste zu sein, da sie "Was ich will" und "Wie ich will" klar voneinander trennt (es hat sogar ein Fragezeichen zwischen ihnen lol). Das Ganzseiten-Caching kann unter Verwendung der vollständigen URL implementiert werden (alle Optionen haben ohnehin das gleiche Problem).

Beim Parameters-in-URL-Ansatz ist der einzige Vorteil eine saubere URL. Sie müssen sich jedoch eine Möglichkeit einfallen lassen, um Parameter zu codieren und verlustfrei zu decodieren. Natürlich kannst du mit URLencode / Decode gehen, aber es wird URLs wieder hässlich machen :)

TEHEK
quelle
1
Das sind zwei verschiedene Ordnungen. Die erste Sortierung erfolgt nach absteigendem Datum und unterbricht die Verbindungen nur nach aufsteigendem Namen. Die zweite Sortierung erfolgt nach aufsteigendem Namen und unterbricht die Verbindungen nur nach absteigendem Datum.
Imran Rashid
Tatsächlich unterscheiden sich die beiden hier angegebenen Beispiel-URLs nicht nur durch das Schreiben, sondern auch durch die Bedeutung. Da Sie einen Pfad angeben, wird nicht garantiert, dass Sie dasselbe finden, wenn Sie zuerst nach links und danach nach rechts abbiegen oder umgekehrt. Allerdings haben Sortierparameter als URL-Pfadteile formale Vorteile gegenüber URL-Parametern, die kommutativ austauschbar sein sollten, ohne die Gesamtbedeutung zu ändern, aber tatsächlich unter Codierungsfallen leiden, wie hier angegeben.
Christian Gosch
7

Ich würde es vorziehen, die Abfrageparameter Offset und Limit zu verwenden.

Offset : für den Index des Elements in der Sammlung.

Limit : für die Anzahl der Artikel.

Der Client kann den Offset einfach wie folgt aktualisieren

offset = offset + limit

für die nächste Seite.

Der Pfad wird als Ressourcenkennung betrachtet. Und eine Seite ist keine Ressource, sondern eine Teilmenge der Ressourcensammlung. Da die Paginierung im Allgemeinen eine GET-Anforderung ist, eignen sich Abfrageparameter eher für die Paginierung als für Header.

Referenz: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page

Sorter
quelle
5

Auf der Suche nach Best Practices bin ich auf diese Website gestoßen:

http://www.restapitutorial.com

Auf der Ressourcenseite befindet sich ein Link zum Herunterladen einer PDF-Datei, die die vollständigen, vom Autor vorgeschlagenen REST-Best Practices enthält. In dem es unter anderem einen Abschnitt über Paginierung gibt.

Der Autor schlägt vor, sowohl die Verwendung eines Range-Headers als auch die Verwendung von Abfragezeichenfolgenparametern zu unterstützen.

Anfrage

Beispiel für einen HTTP-Header:

Range: items=0-24

Beispiel für Abfragezeichenfolgenparameter:

GET http://api.example.com/resources?offset=0&limit=25

Wobei Offset die anfängliche Artikelnummer und das Limit ist die maximale Anzahl der zurückzugebenden Artikel.

Antwort

Die Antwort sollte einen Content-Range-Header enthalten, der angibt, wie viele Elemente zurückgegeben werden und wie viele Elemente insgesamt noch abgerufen werden müssen

Beispiele für HTTP-Header:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

In der PDF-Datei finden Sie einige weitere Vorschläge für spezifischere Fälle.

Mario Arturo
quelle
4

Ich verwende derzeit ein ähnliches Schema in meinen ASP.NET MVC-Apps:

z.B http://application/products/by-date/page/2

speziell ist es: http://application/products/Date/Ascending/3

Ich bin jedoch nicht wirklich zufrieden damit, Paging- und Sortierinformationen auf diese Weise in die Route aufzunehmen.

Die Liste der Artikel (in diesem Fall Produkte) ist veränderbar. Wenn jemand das nächste Mal zu einer URL zurückkehrt, die Paging- und Sortierparameter enthält, haben sich die Ergebnisse möglicherweise geändert. Die Idee http://application/products/Date/Ascending/3einer eindeutigen URL, die auf eine definierte, unveränderliche Reihe von Produkten verweist, geht also verloren.

Steve Willcock
quelle
1
Das erste Problem, bei dem nach mehreren Spalten sortiert wird, gilt meiner Meinung nach für alle drei Methoden. Es ist also für keinen von ihnen wirklich ein Pro / Contra. Zum zweiten Thema: Kann das nicht mit einer Ressource geschehen ? Ein Produkt kann beispielsweise auch bearbeitet / gelöscht werden.
andi
Ich denke, das Sortieren nach mehreren Spalten ist wirklich ein Nachteil für alle drei Methoden, da die URL immer größer und unüberschaubarer wird - daher ein Grund, warum ich überlege, auf formularbasierte Seiten- / Sortierparameter umzusteigen. Für die zweite Ausgabe gibt es meines Erachtens einen grundlegenden konzeptionellen Unterschied zwischen einer eindeutigen dauerhaften Kennung wie einer Produkt-ID und einer vorübergehenden Liste von Produkten. Bei gelöschten Produkten gibt eine Meldung, z. B. "Dieses Produkt existiert nicht im System", etwas Konkretes über dieses Produkt an.
Steve Willcock
1
Das Entfernen aller Paging- und Sortierinformationen von der Route ist gut. Und es in POST-Parameter zu verschieben ist schlecht. Hallo? Frage ist über REST. Wir verwenden POST nicht nur, um die URL in REST zu verkürzen. Verb macht Sinn.
Temoto
1
Persönlich würde ich keine Formularparameter für eine Abfrage verwenden, da fast eine POST- oder PUT-HTTP-Methode erforderlich wäre (da die Anforderung jetzt einen Text enthält). GET scheint mir die geeignetere Methode zu sein, da sowohl POST als auch PUT das Ändern der Ressource implizieren. Aus diesem Grund würde ich der URL weitere Abfrageparameter hinzufügen, wenn eine Sortierung nach mehreren Spalten erforderlich ist.
Paul D. Eden
1

Ich stimme slf eher zu, dass "Seite" nicht wirklich eine Ressource ist. Auf der anderen Seite ist Option 3 sauberer, leichter zu lesen und kann vom Benutzer leichter erraten und bei Bedarf sogar abgetippt werden. Ich bin zwischen Option 1 und 3 hin und her gerissen, sehe aber keinen Grund, Option 3 nicht zu verwenden.

Auch wenn sie gut aussehen, besteht ein Nachteil der Verwendung versteckter Parameter, wie bereits erwähnt, anstelle von Abfragezeichenfolgen oder URL-Segmenten darin, dass der Benutzer keine Lesezeichen setzen oder direkt auf eine bestimmte Seite verlinken kann. Dies kann je nach Anwendung ein Problem sein oder auch nicht, aber nur etwas, das Sie beachten sollten.

wahnsinniger Träumer
quelle
1
In Bezug auf Ihre Erwähnung, leichter zu erraten zu sein, sollte dies keine Rolle spielen. Wenn Sie eine Hypermedia-API erstellen, sollten die Benutzer niemals URIs erraten MÜSSEN.
JR Garcia
0

Ich habe schon einmal Lösung 3 verwendet (ich schreibe eine Menge Django-Apps). Und ich glaube nicht, dass daran etwas falsch ist. Es ist genauso generierbar wie die beiden anderen (falls Sie etwas Massenkratzen oder ähnliches durchführen müssen) und es sieht sauberer aus. Außerdem können Ihre Benutzer URLs erraten (wenn es sich um eine öffentlich zugängliche App handelt), und die Leute mögen es, direkt dorthin zu gelangen, wo sie wollen, und das Erraten von URLs fühlt sich befähigend an.

Alex
quelle
0

Ich verwende in meinen Projekten die folgenden URLs:

http://application/products?page=2&sort=+field1-field2

was bedeutet - "gib mir Seite die zweite Seite geordnet nach Feld1 und dann nach Feld2 absteigend". Oder wenn ich noch mehr Flexibilität brauche, benutze ich:

http://application/products?skip=20&limit=20&sort=+field1-field2
Eugene
quelle
0

Ich verwende in folgenden Mustern, um den nächsten Seitendatensatz zu erhalten. http: // application / products? lastRecordKey =? & pageSize = 20 & sort = ASC

RecordKey ist die Spalte einer Tabelle, die einen sequentiellen Wert in DB enthält. Dies wird verwendet, um jeweils nur eine Seitendaten aus der Datenbank abzurufen. Mit pageSize wird festgelegt, wie viele Datensätze abgerufen werden sollen. sort wird verwendet, um den Datensatz in aufsteigender oder absteigender Reihenfolge zu sortieren.

Susanta Ghosh
quelle