REST Webアプリケーションでのページネーション


329

これは、この質問をより一般的に再構成したものです(Rails固有の部分が削除されています)。

RESTful Webアプリケーションのリソースにページネーションを実装する方法がわかりません。私がと呼ばれるリソースを持っているとproductsすると、次のうちどれが最善のアプローチだと思いますか、そしてその理由は次のとおりです。

1.クエリ文字列のみを使用する

例えば。http://application/products?page=2&sort_by=date&sort_how=asc
ここでの問題は、全ページキャッシュを使用できないことと、URLがあまりクリーンで覚えにくいことです。

2.リソースとしてのページとソート用のクエリ文字列の使用

例えば。http://application/products/page/2?sort_by=date&sort_how=asc
この場合、参照される問題は、それがあるhttp://application/products/pages/1使用して以来、独自のリソースではありませんsort_by=price全く異なる結果をもたらすことができる私はまだページのキャッシュを使用することはできません。

3.リソースとしてのページとソート用のURLセグメントの使用

例えば。http://application/products/by-date/page/2
私は個人的にこの方法を使用しても問題ないと思いますが、これは良い方法ではないと警告されました(理由は示さなかったため、推奨されない理由がわかっている場合はお知らせください)

任意の提案、意見、批判は歓迎以上です。ありがとう。


34
これは素晴らしい質問です。
Iainホルダー

7
おまけの質問:人々は通常どのようにページサイズを指定しますか?
Heiko Rupp 2013

マトリックスパラメータを忘れないでくださいw3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

回答:


66

バージョン3の問題はより「視点」の問題だと思います。ページをリソースまたはページ上の製品として見ていますか。

ページをリソースとして表示する場合、ページ2のクエリは常にページ2を生成するため、それは完全に優れたソリューションです。

ただし、ページ上の製品がリソースとして表示される場合、ページ2の製品が変更される(古い製品が削除されるなど)問題がある場合、この場合、URIは常に同じリソースを返しません。

たとえば、顧客が製品リストページXへのリンクを保存すると、次にリンクが開かれるときに、問題の製品はページXに存在しなくなる可能性があります。


6
まあ、もしあなたが何かを削除した場合、同じURI上に何か他のものがあってはなりません。ページXのすべての製品を削除すると、ページXは引き続き有効である可能性がありますが、ページX + 1の製品が含まれています。したがって、「製品リソースビュー」に表示されている場合、ページXのURIはページX + 1のURIになっています。 」
Fionn

1
>ページをリソースとして表示する場合、ページ2のクエリは常にページ2を生成するため、それは完全に適切なソリューションです。それでも意味がありますか?同じURL(ページ2について言及しているURL)は、リソースとしてどのようなものであっても、常にページ2を生成します。
temoto

2
リソースとしてページを表示するには、新しいページを作成するためにPOST / foo / pageを導入する必要がありますよね?
temoto 2009

18
あなたの答えは「正しい解は1」にスムーズに行きますが、それを述べていません。
temoto 2009

2
私の考えでは、ページはフローティングコンセプトであり、基盤となるドメインとは関係ありません。したがって、リソースと見なすべきではありません。流動的であり、ページの概念がコンテキストによって変化するという意味で浮かんでいます。APIの1人のユーザーは、ページごとに2つの製品しか消費できないモバイルアプリである可能性があり、もう1人は、全体のリストを消費できるマシンアプリです。つまり、ページは基礎となるドメインエンティティ(製品)の「表現」であり、URLの一部として含めることはできません。クエリパラメータとしてのみ。
Kingz 14

106

Fionnに同意しますが、さらに一歩進んで、ページはリソースではなく、リクエストのプロパティであると私は言います。そのため、オプション1のクエリ文字列のみを選択しました。ちょうどいい感じです。Twitter APIが落ち着いて構成されている方法が本当に気に入っています。単純すぎず、複雑すぎず、十分に文書化されています。良くも悪くも、何かを片手にやろうと思っているときの「行きたい」デザインです。


28
+1:クエリ文字列はファーストクラスのリソース識別子ではありません。リソースの順序付けとグループ化を明確にするだけです。
S.Lott

1
@ S.Lottリクエストリソースです。「ファーストクラスのリソース」と呼ばれるものは、彼の論文のセクション5.2.1.1のフィールディングによってとして定義されています。さらに、同じセクションで、Fieldingはリソースの例としてソースコードファイルの最新リビジョンを示しています。どうすればリソースになれますが、最新の10製品は「製品リソースのリクエストのプロパティ」になりますか?あなたの見解はより実用的だと思いますが、RESTfulとは言えないと思います。
edsioufi 2013

@RichApodacaが彼の回答で述べたように、私のコメントは、URLでクエリ文字列を使用するという選択に同意しないという意味ではありません。APIがハイパーメディア駆動型である限り、どちらも実行可能なソリューションです。このページは、RESTの観点からはリソースと見なす必要があることを指摘しています。
edsioufi 2013

37

HTTPには、ページネーションにも適した素晴らしいRangeヘッダーがあります。あなたは送るかもしれません

Range: pages=1

最初のページだけを持っています。それはあなたにページとは何かを考え直すことを強いるかもしれません。たぶんクライアントは異なる範囲のアイテムを望んでいます。範囲ヘッダーは、順序を宣言するためにも機能します。

Range: products-by-date=2009_03_27-

その日よりも新しいすべての製品を入手するため、または

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

その日付より古いすべての製品を入手するため。「0」はおそらく最善の解決策ではありませんが、RFCは範囲の開始に何かを必要としているようです。units = -range_endを解析しないHTTPパーサーがデプロイされている可能性があります。

ヘッダーが(受け入れ可能な)オプションではない場合、最初の解決策(すべてクエリ文字列内)は、ページを処理する方法です。ただし、クエリ文字列を正規化してください(アルファベット順にソート(キー=値)のペア)。これにより、「?a = 1&b = x」および「?b = x&a = 1」の微分問題が解決されます。


34
ヘッダーは一見すると見栄えが良いかもしれませんが、ページの共有は許可されていません(URLをコピーするなど)。そのため、ajaxリクエストの場合、(ajaxによって変更されたページを現在の状態で共有できないため)良い解決策になるかもしれませんが、通常のページネーションには使用しません。
Markus

3
また、範囲ヘッダーはバイト範囲専用です。[HTTPヘッダーの仕様](w3.org/Protocols/rfc2616/rfc2616-sec14.html)のセクション14.35を参照してください。
クリスウェスティン

16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1は、範囲(セクション14.35)およびコンテンツ範囲(セクション14.16)ヘッダーフィールドで範囲単位を使用します。range-unit = bytes-unit | other-range-unit 多分あなたはThe only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.それを言及しているのはあなたの声明と同じではありません。
temoto 2012

1
@Markus REST APIリソースを共有しているときのユースケースは想像できません:)
JakubKnejzlik

@JakubKnejzlik共有は問題ではありませんが、ページングにHTTPヘッダーを使用すると、ページングにHATEOASリンクを使用できなくなります。
xarx 2018年

25

オプション1は、アプリケーションがページ分割を同じリソースの別のビューを生成するための手法と見なす限り、最適と思われます。

そうは言っても、URLスキームは比較的重要ではありません。(すべてのRESTアプリケーションは当然のことながらそうでなければならないため)ハイパーテキスト駆動になるようにアプリケーションを設計している場合、クライアントはそれ自体でURIを構築することはありません。代わりに、アプリケーションがクライアントにリンクを提供し、クライアントがリンクをたどります。

クライアントが提供できる1種類のリンクは、ページネーションリンクです。

これらすべての楽しい副作用は、ページネーションURI構造についての考え方を変えて、来週、まったく異なるものを実装したとしても、クライアントは何も変更せずに作業を続けることができるということです。


3
REST Webサービスでリンクのようなハイパーメディアを使用する際の注意点。
ポールD.エデン

11

私は常にオプション1のスタイルを使用しています。私の場合はとにかくデータが頻繁に変更されるため、キャッシングは問題ではありませんでした。ページのサイズを構成可能にすると、データをキャッシュできなくなります。

覚えにくいURLやわかりにくいURLはありません。私にとって、これはクエリパラメータの適切な使用法です。リソースは明らかに製品のリストであり、クエリパラメータは、リストをどのように表示するか(ソートされているか、どのページか)を伝えているだけです。


1
+1私はあなたが正しいと思うので、クエリパラメータ(オプション1)に行きます
andi

「覚えにくいURLはない」通常、ブックマークは1つしかないため、この観察はRESTアプリケーションでは役に立ちません...ユーザー(またはクライアントアプリ)がURLを「記憶」しようとする場合、APIが落ち着いていないことを示しています。
edsioufi 2013

8

オプション3には特定の順序でパラメーターがあることを誰も指摘していないのは奇妙です。 http // application / products / Date / Descending / Name / Ascending / page / 2 および http // application / products / Name / Ascending / Date / Descending / page / 2

同じリソースを指していますが、URLが完全に異なります。

私にとって、オプション1は「私が欲しいもの」「私が欲しい方法を明確に区別しているので、最も受け入れられそうです(それらの間には疑問符が付いています笑)。フルページキャッシングは、フルURLを使用して実装できます(いずれにしても、すべてのオプションで同じ問題が発生します)。

Parameters-in-URLアプローチを使用した場合の唯一の利点は、クリーンなURLです。ただし、パラメーターをエンコードして可逆的にデコードする方法を考え出す必要があります。もちろん、URLencode / decodeを使用できますが、URLが再び醜くなります:)


1
これらは2つの異なる順序です。1つ目は日付の降順で並べ替え、名前の昇順でのみ関係を解除します。2番目は名前の昇順で並べ替え、日付の降順でのみ関係を解除します。
Imran Rashid、

実際、ここに記載されている2つのURLの例は、書き方だけでなく意味でも異なっています。パスを示しているため、最初に左折し、その後右折した場合、またはその逆の場合に同じことを見つける保証はありません。そうは言っても、URLパスパーツとしての並べ替えパラメーターには、全体的な意味を変更せずに交換可能に交換できるURLパラメーターよりも形式的な利点がありますが、確かに、ここで言うようにトラップのエンコードに悩まされています。
Christian Gosch、2015年

7

クエリパラメータのオフセットと制限を使用したいと思います。

offset:コレクション内のアイテムのインデックス。

limit:アイテムの数。

クライアントは次のようにオフセットを更新し続けることができます

offset = offset + limit

次のページへ。

パスはリソース識別子と見なされます。また、ページはリソースではなく、リソースコレクションのサブセットです。ページネーションは通常GETリクエストであるため、クエリパラメータはヘッダーではなくページネーションに最適です。

リファレンス:https : //metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page


5

私がこのサイトに出くわしたベストプラクティスを探しています:

http://www.restapitutorial.com

リソースページには、作成者が提案したRESTのベストプラクティスをすべて含む.pdfをダウンロードするためのリンクがあります。特にページネーションに関するセクションがあります。

著者は、範囲ヘッダーの使用とクエリ文字列パラメーターの使用の両方にサポートを追加することを提案しています。

リクエスト

HTTPヘッダーの例:

Range: items=0-24

クエリ文字列パラメーターの例:

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

ここで、offsetは最初のアイテム数で、limitは返されるアイテムの最大数です。

応答

応答には、返されるアイテムの数と、まだ取得されていないアイテムの合計数を示すContent-Rangeヘッダーが含まれている必要があります

HTTPヘッダーの例:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

.pdfには、より具体的なケースに関するその他の提案がいくつかあります。


4

私は現在、ASP.NET MVCアプリで次のようなスキームを使用しています。

例えば http://application/products/by-date/page/2

具体的には: http://application/products/Date/Ascending/3

ただし、この方法でページングと情報のソートをルートに含めることには本当に満足していません。

アイテム(この場合は製品)のリストは変更可能です。つまり、次にページングとソートのパラメータを含むURLに誰かが戻ったときに、取得した結果が変更されている可能性があります。したがって、http://application/products/Date/Ascending/3定義された、変更されない一連の製品を指す一意のURLとしての概念は失われます。


1
複数の列での並べ替えの最初の問題は、私の考えでは3つの方法すべてに当てはまります。ですから、それは実際には彼らにとって賛否両論ではありません。2番目の問題について:それはどのリソースに起こりませんか?たとえば、製品を編集/削除することもできます。
アンディ

複数の列での並べ替えは、URLが大きくなり、管理しにくくなるため、3つの方法すべてにとって実際には「欠点」だと思います。そのため、フォームベースのページ/並べ替えパラメーターに移行することを検討しています。2番目の問題については、製品の一時的なリストよりも製品IDのような一意の永続的な識別子の間に根本的な概念上の違いがあると思います。削除された製品の場合、「その製品はシステムに存在しません」などのメッセージで、その製品について具体的なことがわかります。
スティーブウィルコック

1
ルートからすべてのページングとソート情報を削除することは良いことです。そして、それをPOSTパラメーターにプッシュするのはよくありません。こんにちは?質問はRESTについてです。RESTでURLを短くするためだけにPOSTを使用していません。動詞は理にかなっています。
temoto 2009

1
個人的には、POSTまたはPUT HTTPメソッドをほとんど必要とするため、フォームパラメータをクエリに使用しません(リクエストに本文があるため)。POSTとPUTの両方がリソースを変更することを暗示するので、GETは使用するのにより適切な方法のように思えます。そのため、複数の列による並べ替えが必要な場合は、URLにクエリパラメータを追加することになります。
ポールD.エデン

1

「ページ」は実際にはリソースではないという点でslfに同意する傾向があります。一方、オプション3はより簡潔で読みやすく、ユーザーが簡単に推測でき、必要に応じて入力することもできます。オプション1と3の間で引き裂かれましたが、オプション3を使用しない理由はありません。

また、見た目は良いですが、誰かが言及したように、クエリ文字列やURLセグメントではなく、非表示パラメーターを使用することの欠点の1つは、ユーザーがブックマークしたり、特定のページに直接リンクしたりできないことです。これは、アプリケーションに応じて問題になる場合とそうでない場合がありますが、注意が必要なことです。


1
推測しやすいとのことですが、これは問題になりません。ハイパーメディアAPIを構築する場合、ユーザーはURIを推測する必要はありません。
JRガルシア

0

私は以前にソリューション3を使用しました(djangoアプリをたくさん書いています)。そして、私はそれに何か問題があるとは思いません。他の2つと同じように生成でき(大量の削り取りなどを行う必要がある場合)、見た目はすっきりしています。さらに、ユーザーは(パブリックアプリの場合)URLを推測することができ、ユーザーは目的の場所に直接移動できることを望んでおり、URL推測は強力です。


0

私のプロジェクトでは次のURLを使用しています。

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

つまり、「2番目のページをfield1で昇順に並べ、次にfield2で降順に並べてください」という意味です。または、さらに柔軟性が必要な場合は、以下を使用します。

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

0

次のパターンを使用して、次のページレコードを取得します。 http:// application / products?lastRecordKey =?&pageSize = 20&sort = ASC

RecordKeyは、DB内の順次値を保持するテーブルの列です。これは、DBから一度に1つのページデータのみをフェッチするために使用されます。pageSizeは、フェッチするレコードの数を決定するために使用されます。sortは、レコードを昇順または降順に並べ替えるために使用されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.