web-dev-qa-db-ja.com

同じリソースの異なるRESTful表現

私のアプリケーションには/fooにリソースがあります。通常、これは次のようなHTTP応答ペイロードによって表されます。

{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}

クライアントは、このオブジェクトの4つのメンバーすべてを常に必要とするわけではありません。 クライアントがサーバーで表現に必要なものをサーバーに伝えるための(== --- ==)RESTfully semantic方法は何ですか?例えば必要な場合:

{"a": "some text", "b": "some text", "d": "some text"}

それはどのようにGETすべきですか?いくつかの可能性(RESTを誤解している場合は修正を探しています):

  • GET /foo?sections=a,b,d
    • クエリ文字列(結局、query文字列と呼ばれます)は、「この条件に一致するリソースを見つけて通知する」ことを意味し、「このカスタマイズに従ってこのリソースを私に表す」ではないようです」.
  • GET /foo/a+b+d私のお気に入りif REST=セマンティクスはこの問題をカバーしていません。その単純さのためです。
    • HATEOASに違反して、URIの不透明度を壊します。
    • リソース(URIの唯一の意味は1つのリソースを識別すること)と表現の違いを打破しているようです。しかし、これは/widgetsリソースの提示可能なリストを表す/widget/<id>と一貫しているため、議論の余地があります。
  • 制約を緩め、GET /foo/aなどに応答し、クライアントが/fooのコンポーネントごとに要求するように要求します。
    • オーバーヘッドが増加します。/fooに数百のコンポーネントがあり、クライアントがそれらの100を必要とする場合、悪夢になる可能性があります。
    • /fooのHTML表現をサポートしたい場合は、Ajaxを使用する必要があります。これは、最小限のブラウザーなどでクロール、レンダリングできる単一のHTMLページだけが必要な場合に問題になります。
    • HATEOASを維持するには、これらの「サブリソース」へのリンクが、おそらく/foo{"a": {"url": "/foo/a", "content": "some text"}, ...}の他の表現内に存在する必要もあります。
  • GET /fooContent-Type: application/jsonおよび{"sections": ["a","b","d"]}をリクエスト本文に含めます。
    • ブックマークおよびキャッシュ不可。
    • HTTPはGETの本文のセマンティクスを定義していません。正当なHTTPですが、一部のユーザーのプロキシがGETリクエストから本文を削除しないことをどのように保証できますか?
    • 私の RESTクライアントGETリクエストに本文を入れさせないので、それをテストに使用することはできません。
  • カスタムHTTPヘッダー:Sections-Needed: a,b,d
    • 可能であれば、カスタムヘッダーは避けたいです。
    • ブックマークおよびキャッシュ不可。
  • POST /foo/requestsContent-Type: application/jsonおよび{"sections": ["a","b","d"]}をリクエスト本文に含めます。 201付きのLocation: /foo/requests/1を受け取ります。次に、GET /foo/requests/1を使用して、/foo の必要な表現を受け取ります。
    • 不格好;前後と奇妙に見えるコードが必要です。
    • /foo/requests/1は、1回だけ使用され、要求されるまで保持されるだけのエイリアスであるため、ブックマーク不可およびキャッシュ不可。
52
Jordan

私は次のことに決めました:

いくつかのメンバーの組み合わせをサポートしています:各組み合わせの名前を付けます。例えば記事に著者、日付、本文のメンバーが含まれている場合、/article/some-slugはそのすべてを返し、/article/some-slug/metaは著者と日付のみを返します。

多くの組み合わせのサポート:メンバー名をハイフンで区切ります:/foo/a-b-c

どちらの方法でも、組み合わせがサポートされていない場合は404を返します。

建築上の制約

REST

リソースの特定

定義から RESTの:

リソース R 時間的に変化するメンバーシップ関数です MRt)、それはしばらくの間 t 同等のエンティティまたは値のセットにマップします。セット内の値は、リソース表現および/またはリソース識別子である場合があります。

HTTP本文である表現とURLである識別子。

これは非常に重要です。識別子は、他の識別子と表現に関連付けられた単なる値です。これは、識別子→表現のマッピングとは異なります。サーバーは、両方が同じリソースによって関連付けられている限り、任意の識別子を任意の表現にマップできます。

「ユーザー」や「投稿」などのカテゴリを考えることによってビジネスを合理的に説明するリソース定義を思いつくのは開発者次第です。

HATEOAS

完全なHATEOASに本当に関心がある場合は、/foo表現のどこかに/foo/membersへのハイパーリンクを配置できます。その表現には、サポートされているすべてのメンバーの組み合わせへのハイパーリンクが含まれます。

HTTP

URLの definition から:

クエリコンポーネントには、非階層データが含まれています。これは、パスコンポーネントのデータとともに、URIのスキームと命名機関(存在する場合)のスコープ内でリソースを識別するのに役立ちます。

したがって、/foo?sections=a,b,d/foo?sections=bは別個の識別子です。しかし、それらは、同じリソース内で関連付けられている一方で、異なる表現にマッピングされていることができます。

HTTPの404コード 手段 サーバーがURLをマップするものを見つけられなかったこと、URLがどのリソースにも関連付けられていないことではありません。

機能性

ブラウザやキャッシュでスラッシュやハイフンが問題になることはありません。

6
Jordan

クエリ文字列ソリューション(最初の)をお勧めします。他の選択肢に対するあなたの議論は良い議論です(そして私が同じ問題を解決しようとするときに実際に遭遇したものです)。特に、「制約を緩める/ foo/aに応答する」ソリューションcanは限られたケースで機能しますが、実装と消費の両方からAPIに多くの複雑さをもたらし、そうではありませんでした。私の経験では、努力する価値がありました。

一般的な例を使用して、「意味があるように思われる」引数に弱く対抗します。オブジェクトの大きなリストであるリソース(GET /Customers)について考えます。これらのオブジェクトをページングすることは完全に合理的であり、クエリ文字列を使用してそれを行うのは一般的です:例としてGET /Customers?offset=100&take=50。この場合、クエリ文字列は、リストされたオブジェクトのプロパティをフィルタリングしていません。オブジェクトのサブビューのパラメーターを提供しています。

より具体的には、クエリ文字列を使用するためのこれらの基準により、一貫性とHATEOASを維持できると思います。

  • 返されるオブジェクトは、クエリ文字列なしでUrlから返されるものと同じエンティティである必要があります。
  • クエリ文字列のないUriは完全なオブジェクト-使用可能なビューのスーパーセットwith同じUriのクエリ文字列を返す必要があります。したがって、装飾されていないUriの結果をキャッシュすると、完全なエンティティを持っていることがわかります。
  • 特定のクエリ文字列に対して返される結果は確定的である必要があります。これにより、クエリ文字列を持つUrisは簡単にキャッシュ可能になります。

ただし、これらのUrisのreturnの対象は、より複雑な質問になる場合があります。

  • クエリ文字列のみが異なるUrisの異なるエンティティタイプを返すことは望ましくない場合があります(/fooはエンティティですが、foo/aは文字列です)。代替手段は、部分的に入力されたエンティティを返すことです
  • doサブクエリに異なるエンティティタイプを使用する場合、/fooaがない場合、404ステータスは誤解を招く(/foodoes存在します!)、しかし空の応答は同様に混乱するかもしれません
  • 部分的に入力されたエンティティを返すことは望ましくない場合がありますが、エンティティの一部を返すことができないか、混乱を招く可能性があります
  • 強力なスキーマがある場合、部分的に入力されたエンティティを返すことはできない場合があります(aが必須であるが、クライアントがbのみを要求する場合、a、または無効なオブジェクト)

これまで、必要なエンティティの特定の名前付き「ビュー」を定義し、?view=summary?view=totalsOnlyなどのクエリ文字列を許可することで、これを解決しようとしました-順列の数を制限しました。これにより、サービスのコンシューマーにとって「理にかなっている」エンティティーのサブセットの定義が可能になり、文書化することができます。

結局のところ、これは何よりも一貫性の問題に帰着すると思います。クエリ文字列を使用してHATEOASガイダンスに比較的簡単に対応できますが、行う選択はAPI全体で一貫している必要があり、十分に文書化されています。

11
Dan Puzey

実際には、リソースの機能によって異なります。たとえば、リソースがエンティティを表す場合:

/customers/5

ここで、「5」は顧客のidを表します

応答:

{
   "id": 5,
   "name": "John",
   "surename": "Doe",
   "marital_status": "single",
   "sex": "male",
   ...
}

したがって、詳しく調べると、各jsonプロパティは実際には顧客リソースインスタンスのレコードのfieldを表します。消費者がfieldsの一部である部分的な応答を取得したいとします。消費者がリクエストを通じてさまざまなフィールドを選択する機能を望んでいるので、それを見ることができます。これは、彼にとって興味深いが、それ以上ではありません(フィールドの一部が計算が難しい場合、トラフィックまたはパフォーマンスを節約するため)。 。

この状況では、最も読みやすく正しいAPIは(たとえば、nameおよびsurenameのみを取得する)だと思います

/customers/5?fields=name,surename

応答:

{
   "name": "John",
   "surename": "Doe"
}

HTTP/1.1

  • 不正なフィールド名が要求された場合-404(見つかりません)が返されます
  • 異なるフィールド名が要求された場合-異なる応答が生成されますが、これもキャッシュと整合しています。
  • 短所:同じフィールドが要求されているが、フィールド間で順序が異なる場合(例:fields=id,nameまたはfields=name,id)、レスポンスは同じですが、それらのレスポンスは個別にキャッシュされます。

[〜#〜] hateoas [〜#〜]

  • 私の意見では、純粋なHATEOASはこの特定の問題の解決には適していません。それを達成するためには、fieldの組み合わせの順列ごとに個別のリソースが必要です。リソースには8つのフィールドがあり、必要になります enter image description here 順列!).
  • すべての順列ではなくfieldsのリソースのみをモデル化する場合、パフォーマンスに影響があります。往復回数を最小限にしたい。
4
Genry

リクエストヘッダーapplication/vnd.com.mycompany.resource.rep2で2番目のベンダーのメディアタイプを使用できますが、これをブックマークすることはできませんが、クエリパラメータはキャッシュできません(/ foo?sections = a、b、c )あなたはマトリックスパラメーターを見ることができますが、この質問に関してはそれらはキャッシュ可能でなければなりません RLマトリックスパラメーターvs.リクエストパラメーター

2
cproinger

A、b、cがロールプロパティの管理者のようなリソースのプロパティである場合、正しい方法はGET /foo?sections=a,b,dを提案した最初の方法です。この場合、fooコレクション。それ以外の場合、a、bおよびcがfooコレクションの単一リソースである場合、その後の方法は、一連のGET要求/foo/a /foo/b /foo/cを実行することです。このアプローチは、あなたが言ったように、リクエストのペイロードが高いですが、Restfullアプローチに従う正しい方法です。 URLのプラス文字には特別な意味があるため、私はあなたが作成した2番目の提案を使用しません。

もう1つの提案は、GETとPOSTの使用を中止し、fooコレクションに次のようなアクションを作成することです:/foo/filterまたは/foo/selectionまたは表す動詞コレクションに対するアクション。このように、postリクエスト本文を使用して、リソースのjsonリストを渡すことができます。

2
LuckyStarr