REST APIを設計するとき、私は通常、システムのリソースを特定することから始めます。例として、それぞれが複数のblogs
を持ち、それぞれが複数のposts
を持つ複数のcomments
を管理するとします。
このような場合の標準のREST APIは、次のようなエンドポイントをいくつか持つことになります。
/blogs //all blogs
/blogs/:blog_id/posts //all post of blog :blog_id
/blogs/:blog_id/posts/:post_id/comments //all comments of post :post_id
これは基本的なREST=機能を実装するための非常にシンプルで効果的な方法ですが、その後に続くことはあまり明確ではないと思います。
1-クライアントがブログのサブセットを要求する可能性を公開する適切な方法は何ですか?
可能性は:
/blogs?blog_id=:blog_id1&blog_id=:blog_id2
あなたはそれについてどう思いますか?
2-クライアントが1つのリクエストですべての投稿を含むブログを要求する可能性を公開する適切な方法は何ですか?
可能性は:
/blogs?_with=posts
あなたはそれについてどう思いますか?
3-質問2のアプローチが気に入ったら、サブリソースのサブリソースについてはどうですか?
/blogs?_with=posts.comments
これは受け入れられますか?このようにしていくと、ネストされた要素がいくつ必要になるのでしょうか。
どうですか
/blogs?_with=posts.comments.authors.posts.comments
この種のエンドポイント悪い設計を検討するいくつかの客観的な理由がありますか(設計的には、パフォーマンスなどについて話していません)。
この質問は主に主観的であると理解していますが、多種多様なシステムで特定および実装できる「ベストプラクティス」があると思います。
フィルタリング
クエリパラメータはこれに適したセマンティックフィットであり、多くのAPIがこの方法で実装されています。例 OpenStack API を参照してください。各フィルターが単一の値を単一の属性に直接マッピングするクエリパラメーターを使用すると、フィルターを簡単に組み合わせることができますどちらか 2つの結果セット間の論理積(AND)を表すor a論理和(OR)。ただし、両方を同時に使用することはできません。例えば。次のリクエストは2つの方法で解釈できます。
GET /blogs/:blog_id/posts?created_at=today&author=12
リクエストは次のいずれかになります。今日作成された特定のブログのすべての投稿を教えてくださいand著者IDが12であるか、次のように言うことができます:作成された特定のブログのすべての投稿を教えてください今日or作成者IDが12である。使用されている方のどちらでも使用できると想定できるconvention特定のAPI内では、ほとんどの人はアンパサンド文字がANDアプローチを好むと当然理解していると思うしかし、代わりに2つのセットの和集合にならない理由はありません。
リソースを照会するときに、どの結果が形成されるかを判別するためのより複雑なロジックが必要になる場合があります。この場合、フィルターのクエリパラメーターの設定方法を少し賢くする必要があり、おそらく何らかの形式のクエリ言語を実装する必要があります。 URLの実際のパラメーターはリソースの属性にマップされませんが、値には属性への参照が含まれます。また、 '>'、 '<'などの演算子や、ANDやORなどのキーワードなど、言語が持つ他のさまざまなものが含まれます。 。例をご覧ください JIRA JQL :
GET /blogs/:blog_id/posts?query=created_at IS today AND (author IS 1 OR author IS 2)
アプローチを組み合わせることができるので、クエリパラメータ/属性の1対1のマッピングを維持しながら、より堅牢なフィルタリングをサポートできます。
GET /blogs/:blog_id/posts?created_at=today&author=in:1,2
概念的な観点からは、クエリパラメータを使用することと、パスに文字列を追加することとの間に大きな違いはないと思います。次のように、最初のリクエストを簡単に表すことができます。
GET /blogs/:blog_id/posts/created_at/today/author/12
このアプローチをサポートすることは、ルートを照合するだけでなく、クエリパラメータを使用するだけでなく、パスの特定のセクションが「属性」または「値」であるかどうかを判断するのが少し難しいと感じますが、マイレージは異なる場合があります。簡単に認識/使いやすいように、より基本的なフィルターを実際にパス上に表示したい場合があるため、これについて主に説明します。
GET /blogs/:blog_id/posts/new
そのリクエストは、このリクエストと同じ結果を返す可能性があります。
GET /blogs/:blog_id/posts?created_at=today
埋め込みデータ
あなたが提案していることを正確に実行するシステムがあります。たとえば league/fractal を参照してください。それらは少し遠くまで進んで、埋め込みデータにパラメータを渡してそれをフィルタリングすることができます(上からのアプローチと組み合わせることができます)。つまり、次のようなリクエストを行うことができます。
GET /blogs?include=posts.comments:created(today)
このリクエストは、すべてのブログとその投稿、および「今日」に作成されたコメントのみを返します(すべての投稿のすべてのコメントだけを返します)。
リソースのセットのサイズは、APIの応答性を制限する要因となる可能性があるため、40の関連リソースのチェーンがある最上位のリソースがある場合、過負荷にならないようにリクエストで注意する必要がある場合があります。リソースサーバー、または関係チェーンの深さの上限を指定できます。
ページネーションへの影響に関する懸念もあります。リクエストしている最上位のリソースにページ番号を付ける場合、埋め込みリソースにもページ番号を付けますか? [〜#〜] hateoas [〜#〜] -スタイルのリンク関係をサポートすると、クライアントが参照を取得して、関心のあるページ付けされた埋め込みデータのリストをさらに詳しく調べて、問題を軽減できる場合があります。 、しかし考慮のための要因です。
私が見た3つのオプションがあります。 1つ目は、提案したことを実行することです。これにより、ほとんどの場合、Long[]
またはサーバーサイドのsomesuch。 2番目は完全にクエリ言語ではありません。IDの区切られたリストを文字列として許可し、それをサーバーで分割します。 3番目は、@ JeffLambertによって提案された本格的なクエリ言語ルートです。クエリ言語が必要ない場合は、他のオプションのいずれかを選択するすべての人の精神的負担は少なくなります。人間がハッキング可能なURLが必要な場合は、カンマ区切りのリストが適しています。一部のフレームワークでは、同じ名前の複数のクエリパラメータを簡単に作成できます。
私は通常、それがinclude=[foo, bar, baz.blop]
ですが、クエリパラメータインクルードに名前を付ける魔法はありません。
参照するのに意味のある要素の数については、一般に、@ RobertHarveyがデメテルの法則に導かれることに同意します。深さ1までの任意の数のインクルードを許可します。 include=[foo, bar, baz, blort, ...]
なしinclude=[baz.blop]
。もちろん、それはユーザーのニーズによって異なります。パフォーマンスが重要なアプリケーションで、命がけであり、1つのクエリで深さ12にネストする必要がある場合について考えます。それはルールではなく例外であるべきです。 Webは、ほとんどの人が認めているよりもパフォーマンスが高く、通常は複数のクエリで問題ありません。
理想的な世界では、include
パラメータをまったく気にせず、ベースオブジェクトからfoo、bar、bazなどへのリンクを提供するだけです。実際に測定されたinclude
パスを開始する前に複数のクエリを実行すると、パフォーマンスの問題が発生します。