オブジェクト内のアイテムの総数を返すのに最適なRESTfulメソッドは何ですか?


139

私が関わっている大規模なソーシャルネットワーキングウェブサイト用のREST APIサービスを開発しています。これまでのところ、うまく機能しています。私が発行することができGETPOSTPUTおよびDELETEオブジェクトのURLへのリクエストや自分のデータに影響を与えます。ただし、このデータはページングされます(一度に30結果に制限されます)。

しかし、私のAPIを介して言う、メンバーの総数を取得するための最良のRESTful方法は何でしょうか?

現在、私は次のようなURL構造にリクエストを発行しています。

  • / api / membersメンバーのリストを返します(上記のように一度に30)
  • / api / members / 1 —使用されるリクエストメソッドに応じて、単一のメンバーに影響します

私の質問は、同様のURL構造を使用してアプリケーションのメンバーの総数を取得するにはどうすればよいですか?id(FacebookのGraph APIと同様に)フィールドだけを要求して結果をカウントすることは、30の結果のスライスしか返されないため、明らかに効果がありません。


回答:


84

/ API / usersへの応答はページングされて30レコードのみを返しますが、応答にレコードの総数やその他の関連情報(ページサイズ、ページ番号/オフセットなど)を含めることを妨げるものは何もありません。 。

StackOverflow APIは、同じ設計の良い例です。これは、Usersメソッドのドキュメントです-https://api.stackexchange.com/docs/users


3
+1:フェッチ制限が課せられる場合、間違いなく最もRESTfulなこと。
ドナルフェロー

2
@bzim rel = "next"のリンクがあるため、フェッチする次のページがあることがわかります。
Darrel Miller

4
@Donal「次の」relはIANAに登録されていますiana.org/assignments/link-relations/link-relations.txt
Darrel Miller

1
@Darrel-はい、ペイロード内の任意のタイプの「次の」フラグで実行できます。コレクションアイテムの合計数をレスポンスに含めることはそれ自体が価値があり、まったく同じように「次の」フラグとして機能するように感じます。
Franci Penov

5
アイテムのリストではないオブジェクトを返すことはREST APIの適切な実装ではありませんが、RESTは結果の部分的なリストを取得する方法を提供していません。したがって、それを尊重するには、ヘッダーを使用して、合計、次のページトークン、前のページトークンなどの他の情報を送信する必要があると思います。私はそれを試したことはなく、他の開発者からのアドバイスが必要です。
Loenix 2016年

74

この種のコンテキスト情報には、HTTPヘッダーの使用を好みます。

要素の総数については、X-total-countヘッダーを使用します。
次のページ、前のページなどへのリンクには、http Linkヘッダーを使用します。http :
//www.w3.org/wiki/LinkHeader

Githubも同じ方法で行います:https : //developer.github.com/v3/#pagination

私の意見では、ハイパーリンクをサポートしないコンテンツ(つまり、バイナリ、画像)を返すときにも使用できるため、よりクリーンです。


5
RFC6648は、標準化されていないパラメーターの名前の前に文字列を付けるという規則を廃止しますX-
JDawg

70

私は最近、このおよび他のRESTページング関連の質問についていくつかの広範な研究を行っており、ここに私の発見のいくつかを追加することは建設的だと思いました。私はページングについての考えとそれらが密接に関連しているのでカウントを含めるように質問を少し広げています。

ヘッダー

ページングメタデータは、応答ヘッダーの形式で応答に含まれます。このアプローチの大きな利点は、応答ペイロード自体が、要求者が要求していた実際のデータに過ぎないことです。ページング情報に関心のないクライアントが応答を処理しやすくします。

合計数などのページング関連情報を返すために、実際に使用されている一連の(標準およびカスタム)ヘッダーがあります。

X合計数

X-Total-Count: 234

これは、私が実際に見つけたいくつかの APIで使用されています。このヘッダーのサポートをループバックなどに追加するためのNPMパッケージもあります。一部の記事では、このヘッダーの設定も推奨しています。

多くの場合、Linkヘッダーと組み合わせて使用​​されます。これはページングにはかなり良いソリューションですが、合計カウント情報が不足しています。

リンク

Link: </TheBook/chapter2>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

私は、一般的なコンセンサスが使用することを、このテーマに多くのことを読んでから、感じLinkヘッダを使用してクライアントにページングリンクを提供するためにrel=nextrel=previousその他これに伴う問題は、それがある、がありますどのように多くの合計レコードの情報が欠けているということです多くのAPIがこれをX-Total-Countヘッダーと組み合わせる理由。

または、一部のAPIやJsonApi標準などはLink形式を使用しますが、情報をヘッダーではなく応答エンベロープに追加します。これにより、メタデータへのアクセスが簡素化され(および合計カウント情報を追加する場所が作成されます)、実際のデータ自体に(エンベロープを追加することにより)アクセスする複雑さが増します。

コンテンツ範囲

Content-Range: items 0-49/234

Rangeヘッダーというブログ記事で宣伝されているので、私は(ページネーションのために)あなたを選びます!。著者は、ページ分割にRangeand Content-Rangeヘッダーを使用することを強く主張します。これらのヘッダー RFCを注意深く読むとバイトの範囲を超えてそれらの意味を拡張することは実際にはRFCによって予期されていて、明示的に許可されていることがわかります。のitems代わりにのコンテキストで使用した場合bytes、Rangeヘッダーは実際に、特定の範囲のアイテムを要求する方法と、応答アイテムが関連する合計結果の範囲を示す方法の両方を提供します。このヘッダーは、合計数を表示する優れた方法も提供します。そして、それは主に1対1をページングにマップする真の標準です。野生で使用されます。

封筒

私たちのお気に入りのQ&A Webサイトからのものを含む多くのAPIは、データに関するメタ情報を追加するために使用されるデータのラッパーであるエンベロープを使用します。また、ODataおよびJsonApi標準はどちらも応答エンベロープを使用します。

これ(imho)の大きな欠点は、実際のデータがエンベロープのどこかにある必要があるため、応答データの処理がより複雑になることです。また、そのエンベロープにはさまざまな形式があり、適切な形式を使用する必要があります。これは、ODataとJsonApiからの応答エンベロープが大きく異なり、ODataが応答の複数のポイントでメタデータを混合していることを示しています。

別のエンドポイント

これは他の回答で十分カバーされていると思います。複数のタイプのエンドポイントがあるため、これは混乱を招くというコメントに同意するため、これについてはあまり調査しませんでした。すべてのエンドポイントがリソースの(コレクション)を表すのが最も良いと思います。

さらなる考え

応答に関連するページングメタ情報を伝達するだけでなく、クライアントが特定のページ/範囲をリクエストできるようにする必要もあります。この側面も見て、まとまりのある解決策が得られるのは興味深いことです。ここでも、ヘッダー(Rangeヘッダーは非常に適しているようです)、またはクエリパラメーターなどの他のメカニズムを使用できます。一部の人々は、いくつかのユースケース(例では意味をなさない可能性がある、別々のリソースとして結果のページを処理することを提唱/books/231/pages/52、私のような頻繁に使用されるリクエストパラメータの野生の範囲を選択することになった。pagesizepage[size]およびlimitサポートに加え、などRangeのヘッダを(とリクエストパラメータとして同じように)。


私は特にRangeヘッダーに興味がありましbytesたが、範囲の型として以外のものを使用することが有効であるという十分な証拠を見つけることができませんでした。
VisioN 2018

2
私は明確な証拠はで見つけることができると思いRFCのセクション14.5acceptable-ranges = 1#range-unit | "none"私は、この製剤は、明示的以外のレンジユニットの余地が考えるbytes、スペック自体のみ定義けれどもbytes
Stijn de Witt

24

実際のアイテムが必要ない場合の代替

Franci Penovの回答は確かに最善の方法です。そのため、要求されたエンティティに関するすべての追加のメタデータと共に常にアイテムを返すことができます。それはそれが行われるべき方法です。

ただし、すべてのデータを返す必要がない場合があるため、すべてのデータを返すことは意味をなさない場合があります。多分あなたが必要とするすべてはあなたの要求されたリソースに関するそのメタデータです。合計数やページ数などのようなものです。このような場合、常にURLクエリでサービスにアイテムを返さないで、次のようなメタデータだけを返すように指示できます。

/api/members?metaonly=true
/api/members?includeitems=0

または似たような...


10
この情報をヘッダーに埋め込むと、カウントを取得するだけのHEADリクエストを作成できるという利点があります。
felixfbecker

1
@felixfbecker正確に、ホイールを再発明し、あらゆる種類の異なるメカニズムでAPIを
散らかしてくれてありがとう

1
@EralpBホイールを再発明し、APIを散らかしてくれてありがとう!?HEADはHTTPで仕様化されています。metaonlyまたはincludeitemsありません。
felixfbecker 2017年

2
@felixfbeckerは「正確に」あなたのためのものであり、残りはOPのためのものです。混乱させて申し訳ありません。
EralpB 2017年

RESTはすべて、HTTPを活用し、それを可能な限り意図したものに利用することです。この場合、Content-Range(RFC7233)を使用する必要があります。特にHEADでは機能しないため、体内の解決策は良くありません。ここで提案されているように新しいヘッダーを作成することは不必要で間違っています。
Vance Shipley

23

HEADリクエストへの応答として、カスタムHTTPヘッダーとしてカウントを返すことができます。このように、クライアントがカウントのみを必要とする場合、実際のリストを返す必要はなく、追加のURLは必要ありません。

(または、エンドポイント間で制御された環境にいる場合は、COUNTなどのカスタムHTTP動詞を使用できます。)


4
「カスタムHTTPヘッダー」?これは、いくぶん意外であるという見出しの下になり、これは、RESTful APIが本来あるべきだと私が考えることに反しています。結局のところ、それは当然のことです。
ドナルフェロー

21
@Donal知っています。しかし、良い答えはすべてすでにとられています。:(
bzlm

1
私も知っていますが、他の人に答えてもらう場合もあります。または、他の方法よりも最善の方法で行う必要がある理由の詳細な説明など、他の方法で寄稿を改善します。
ドナルフェロー

4
制御された環境では、内部で使用され、開発者のAPIポリシーに基づいているため、これは当然のことです。これはいくつかの場合に良い解決策であり、異常な可能性のある解決策のメモとしてここに置いておく価値があります。
James Billingham 2013年

1
私はこの種のもののためにHTTPヘッダーを使用するのがとても好きです(それは本当にそれが属している場所です)。この場合、標準のLinkヘッダーが適切な場合があります(Github APIがこれを使用します)。
マイクマルカッチ2014

11

私は同じようにヘッダーを追加することをお勧めします:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

詳細については、以下を参照してください。

https://github.com/adnan-kamili/rest-api-response-format

Swaggerファイルの場合:

https://github.com/adnan-kamili/swagger-response-template


7

"X-"-Prefixは廃止されました。(参照:https : //tools.ietf.org/html/rfc6648

私たちは、範囲内の改ページをマップするための最善の策であるとして「受け入れ-範囲を」が見つかりました:https://tools.ietf.org/html/rfc7233#section-2.3 「レンジユニット」とのどちらか」「バイト」であるか、またはありトークン"。どちらもカス​​タムデータ型を表していません。(参照:https : //tools.ietf.org/html/rfc7233#section-4.2)それでも、

HTTP / 1.1実装は、他の単位を使用して指定された範囲を無視してもよい(MAY)。

つまり、カスタムレンジユニットの使用はプロトコルに違反しませんが、無視される場合があります。

この方法では、Accept-Rangesを「メンバー」または任意の範囲単位タイプに設定する必要があると予想されます。さらに、Content-Rangeを現在の範囲に設定します。(参照:https : //www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12

どちらの方法でも、RFC7233(https://tools.ietf.org/html/rfc7233#page-8)の推奨に従い、200ではなく206を送信します。

すべての前提条件が真であり、サーバー
がターゲットリソースのRange ヘッダーフィールドをサポートし、指定された範囲が
有効かつ充足可能である場合(セクション2.1で定義)、サーバー
は206(部分的コンテンツ)応答を送信する必要があります(SHOULD ) セクション4で定義されているように、要求さ
れた充足可能
範囲に対応する1 つ以上の部分表現を含むペイロード

したがって、結果として、次のHTTPヘッダーフィールドが作成されます。

部分的なコンテンツの場合:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

完全なコンテンツ:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20

3

を追加するのが最も簡単なようです

GET
/api/members/count

メンバーの総数を返します


11
良い考えではありません。あなたは、クライアントに彼らのページでページ付けを構築するための2つのリクエストをすることを義務づけます。リソースのリストを取得する最初のリクエストと、合計をカウントする2番目のリクエスト。
Jekis 2013

私はそれは良いアプローチだと思います... jsonとして結果のリストだけを返し、クライアント側でコレクションのサイズをチェックすることもできるので、そのようなケースは愚かな例です...さらに/ api / members / countを指定してから/ api /メンバーオフセット= 10&限界= 20?
のMichałZiobro

1
また、多くの種類のページネーションではカウントが不要(無限スクロールなど)であることに
注意してください。

2

新しいエンドポイントはどうですか> / api / members / countは、Members.Count()を呼び出して結果を返すだけです


27
カウントを明示的なエンドポイントに指定すると、スタンドアロンのアドレス指定可能なリソースになります。これは機能しますが、APIを初めて使用する人にとって興味深い質問になります-コレクションメンバーの数はコレクションとは別のリソースですか?PUTリクエストで更新できますか?それは空のコレクションに存在しますか、それともアイテムがそこにある場合のみですか?場合はmembers、コレクションはにPOSTリクエストによって作成することができ/api、う/api/members/countとしても副作用として作成される、または私はそれを要求する前に、それを作成するための明示的なPOSTリクエストを行う必要がありますか?:-)
Franci Penov

2

時々フレームワーク($ resource / AngularJSなど)はクエリ結果として配列を必要とし、{count:10,items:[...]}この場合のように実際に応答が得られない場合があります。

PS実際には$ resource / AngularJSでそれを行うことができますが、いくつかの調整が必要です。


それらの微調整は何ですか?彼らは、このような質問に参考になる:stackoverflow.com/questions/19140017/...
JBCP

Angularはクエリ結果として配列を要求しません。オプションのオブジェクトプロパティでリソースを構成するだけです。isArray: false|true
RémiBecheras

0

あなたはcountsリソースとして考えることができます。URLは次のようになります。

/api/counts/member

-1

ページ分割されたデータをリクエストするとき、ページサイズが明示的にわかっているので(明示的なページサイズパラメータ値またはデフォルトのページサイズ値によって)、応答としてすべてのデータを取得したかどうかがわかります。応答するデータがページサイズよりも少ない場合、データ全体が得られます。完全なページが返されたら、別のページをもう一度要求する必要があります。

カウント用に個別のエンドポイント(またはパラメーターcountOnlyを使用した同じエンドポイント)があることを好みます。なぜなら、適切に開始されたプログレスバーを表示することで、エンドユーザーが長時間/時間のかかるプロセスに備えることができるからです。

各応答でデータサイズを返したい場合は、pageSizeとオフセットも記載されている必要があります。正直に言うと、最善の方法はリクエストフィルターも繰り返すことです。しかし、応答は非常に複雑になりました。したがって、私はカウントを返すために専用のエンドポイントを好みます。

<data>
  <originalRequest>
    <filter/>
    <filter/>
  </originalReqeust>
  <totalRecordCount/>
  <pageSize/>
  <offset/>
  <list>
     <item/>
     <item/>
  </list>
</data>

私のクーレージ、既存のエンドポイントよりもcountOnlyパラメータを優先します。したがって、指定すると、応答にはメタデータのみが含まれます。

endpoint?filter = value

<data>
  <count/>
  <list>
    <item/>
    ...
  </list>
</data>

endpoint?filter = value&countOnly = true

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