60

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

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

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

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

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

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

5 に答える 5

14

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

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

より具体的には、クエリ文字列の使用に関する次の基準により、一貫性と HATEOAS を維持できると言えます。

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

ただし、これらの Uris に対して何を返すかについては、より複雑な問題が生じることがあります。

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

過去に、必要なエンティティの特定の名前付き「ビュー」を定義し、?view=summaryまたはのようなクエリ文字列を許可して?view=totalsOnly、順列の数を制限することで、これを解決しようとしました。これにより、サービスの利用者にとって「意味のある」エンティティのサブセットを定義し、文書化することもできます。

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

于 2013-04-29T08:15:20.717 に答える
8

次のことを決めました。

少数のメンバーの組み合わせのサポート: 各組み合わせに名前を付けます。たとえば、記事に作成者、日付、および本文のメンバーがある場合、/article/some-slugすべてが返され、作成/article/some-slug/meta者と日付のみが返されます。

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

404いずれにせよ、組み合わせがサポートされていない場合はa を返します。

アーキテクチャ上の制約

休み

リソースの識別

RESTの定義から:

リソースRは時間的に変化するメンバーシップ関数M R ( t ) であり、これは時間tに対して等価なエンティティまたは値のセットにマップされます。セット内の値は、リソース表現および/またはリソース識別子である場合があります。

表現は HTTP 本体であり、識別子は URL です。

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

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

ハテオアス

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

HTTP

URLの定義から:

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

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

HTTP の404コード、URL がどのリソースにも関連付けられていないということではなく、サーバーが URL をマップするものを見つけられなかったことを意味します。

機能性

スラッシュやハイフンで問題が発生するブラウザーやキャッシュはありません。

于 2013-04-30T02:52:10.247 に答える
6

実際には、リソースの機能に依存します。たとえば、リソースがエンティティを表す場合:

/customers/5

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

応答:

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

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

この状況で、最も読みやすく正しい API は次のようになると思います (たとえば、nameSurenameのみを取得します) 。

/customers/5?fields=name,surename

応答:

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

HTTP/1.1

  • 不正なフィールド名が要求された場合 - 404 (Not Found) が返されます
  • 異なるフィールド名が要求された場合 - 異なる応答が生成され、これもキャッシングと一致します。
  • 短所: 同じフィールドが要求されたが、フィールド間で順序が異なる場合 (例:fields=id,nameまたはfields=name,id)、応答は同じですが、それらの応答は別々にキャッシュされます。

ハテオアス

  • 私の意見では、純粋な HATEOAS はこの特定の問題を解決するには適していません。これを実現するには、フィールドの組み合わせの順列ごとに個別のリソースが必要です。これは、API を大幅に肥大化させるため、やり過ぎです (たとえば、リソースに 8 つのフィールドがある場合、順列が必要になりますここに画像の説明を入力!)。
  • すべての順列ではなく、フィールドのみのリソースをモデル化すると、パフォーマンスに影響があります。たとえば、往復の回数を最小限に抑えたい場合などです。
于 2015-03-27T10:25:12.457 に答える
2

a、b、c がロール プロパティの admin のようなリソースのプロパティである場合、適切な使用方法は、最初に提案した方法です。 GET /foo?sections=a,b,dこの場合、fooコレクションにフィルターを適用するためです。それ以外の場合、a、b、および c がコレクションの単一のリソースである場合foo、一連のGETリクエストを実行する方法が続きます/foo/a /foo/b /foo/c。あなたが言ったように、このアプローチはリクエストのペイロードが高くなりますが、Restfull アプローチに従うのは正しい方法です。URL の plus char には特別な意味があるため、2 番目の提案は使用しません。

もう 1 つの提案は、GET と POST の使用を放棄し、次のfooようなコレクションのアクションを作成することです: /foo/filteror/foo/selectionまたはコレクションのアクションを表す任意の動詞。このように、ポスト リクエスト ボディを使用すると、必要なリソースの json リストを渡すことができます。

于 2013-04-29T08:04:29.040 に答える