検索はどのようにRESTfulインターフェースに適合しますか?


137

RESTfulインターフェースを設計する場合、要求タイプのセマンティクスは設計に不可欠であると見なされます。

  • GET-コレクションの一覧表示または要素の取得
  • PUT-コレクションまたは要素を置き換えます
  • POST-コレクションまたは要素を作成する
  • DELETE -まあ、ERM、コレクションや要素を削除

ただし、これは「検索」の概念をカバーしていないようです。

たとえば、求人検索サイトをサポートする一連のWebサービスの設計では、次の要件があります。

  • 個々の求人広告を取得する
    • GETdomain/Job/{id}/
  • 求人広告を作成
    • POSTdomain/Job/
  • 求人広告の更新
    • PUT todomain/Job/
  • 求人広告の削除
    • DELETEdomain/Job/

「Get All Jobs」も簡単です。

  • GETdomain/Jobs/

しかし、仕事の「検索」はこの構造にどのように分類されますか?

あなたは可能性があり、それは「リストコレクション」のバリエーションの主張として実装します。

  • GETdomain/Jobs/

ただし、検索複雑になる可能性があり、長いGET文字列を生成する検索を作成することは完全に可能です。つまり、ここSOの質問を参照すると、約2000文字よりも長いGET文字列の使用に問題があります。

例として、ファセット検索があります-「ジョブ」の例を続けます。

「テクノロジー」、「役職」、「規律」、およびフリーテキストキーワード、就業年齢、場所、給与などのファセットでの検索を許可できます。

流動的なユーザーインターフェイスと多数のテクノロジと役職により、検索に多数のファセットの選択肢を含めることが可能です。

この例をジョブではなくCVに微調整すると、さらに多くのファセットがもたらされ、100個のファセットが選択された検索、または50文字の長さ(たとえば役職、大学名、雇用者名)。

そのような状況では、検索データが正しく送信されるようにするために、PUTまたはPOSTを移動することが望ましい場合があります。例えば:

  • POSTdomain/Jobs/

しかし意味的には、これはコレクションを作成するための命令です。

これを検索の作成として表現することもできます。

  • POSTdomain/Jobs/Search/

または(以下のburninggrammaで示唆されているように)

  • POSTdomain/JobSearch/

意味的には理にかなっているように思えるかもしれませんが、実際には何も作成しておらず、データを要求しています。

したがって、セマンティック上はGETですが、GETは必要なものをサポートすることを保証されていません。

ですから、質問は-可能な限りRESTfulなデザインを忠実に保ちながら、HTTPの制限内に収まるようにしながら、検索に最適なデザインは何ですか?


3
私はしばしばGET を使用するつもりdomain/Jobs?keyword={keyword}です。これは私にはうまくSEARCHいきます:)私の希望は、動詞が標準になることです。programmers.stackexchange.com/questions/233158/...
Knerd

はい、ささいな例では問題ないことがわかります。しかし、私たちが構築しているツールでは、実際にはGET文字列が2000文字よりも長くなる複雑な検索に終わるのは、それほど信じられないことではありません。それで何?
ロブベイリー14年

実際、非常に良い点です。圧縮技術の指定についてはどうですか?
Knerd 14年

2
ボディを使用したGETはHTTP仕様で許可されており、ミドルウェアでサポートされている場合とサポートされていない場合があります(そうでない場合があります);)。これはStackexchangeで定期的に表示されます。stackoverflow.com/questions/978061/http-get-with-request-body
ロブ14年

2
最終的に、POST JobSearchで実際の検索エンティティを作成し、jobSearchIdを返しました。次に、GET jobs?jobSearch = jobSearchIdは、実際のジョブコレクションを返します。
セラド14年

回答:


93

GETリクエストには、他のソリューションより優れた利点があることを忘れないでください。

1)GETリクエストはURLバーからコピーでき、検索エンジンによってダイジェストされ、「フレンドリー」です。「フレンドリ」とは、通常、GETリクエストがアプリケーション内の(べき等)を変更しないことを意味します。これは、検索の標準的なケースです。

2)これらの概念はすべて、ユーザーと検索エンジンだけでなく、アーキテクチャのAPI設計の観点からも非常に重要です。

3)POST / PUTを使用して回避策を作成すると、現在考えていない問題が発生します。たとえば、ブラウザの場合、戻るボタン/ページの更新/履歴をナビゲートします。これらはもちろん解決できますが、それは別の回避策となり、別の回避策になります...

これらすべてを考慮すると、私のアドバイスは次のようになるでしょう。

a)巧妙なパラメーター構造使用て、GETに収まることができるはずです。極端な場合は、このGoogle検索のような戦術に行くこともできますが、ここでも多くのパラメータを設定していますが、それは非常に短いURLです。

b)JobSearchのようなアプリケーションに別のエンティティを作成します。非常に多くのオプションがあると仮定すると、これらの検索を同様に保存し、管理する必要がある可能性が高いため、アプリケーションをクリアします。JobSearchオブジェクトはエンティティ全体として作業できます。つまり、テスト /使用が簡単になります。


個人的に、私はすべての爪と戦い、a)でそれを成し遂げようとします。すべての希望が失われたら、涙を浮かべてオプションb)に戻ります。


4
明確にするために、この質問はWebサイトの設計ではなく、Webサービスの設計に関するものであることを意図しています。そのため、ブラウザーの動作は質問の解釈のより広い範囲に関心がありますが、説明されている特定のケースでは重要ではありません。(しかし、興味深い点)。
ロブベイリー14年

@RobBaillieはい、ブラウザは単なるユースケースでした。検索全体がURL文字列で表されるという事実を表現したかったのです。これは、回答の後半にある他のポイントとともに、ユーザビリティに多くの快適さをもたらします。
p1100i 14年

ポイントbで、これはPOSTへの私自身の参照の単純なバリエーションでdomain/Jobs/Search/、多分domain/JobsSearch/代わりに、または何か違うことを意味しましたか?明確にできますか?
ロブベイリー14年

7
RESTがソリューションの一部ではなく、問題の一部であるという印象を受けるのはなぜですか?
JensG 14年

1
GETがapplication等である間、「GETリクエストはアプリケーション内の何も変更すべきではありません(べき等)」、関連する単語はここでは「安全」です。べき等とは、リソースに対してGETを2回実行することは、そのリソースに対してGETを1回実行することと同じことを意味します。PUTもi等ですが、安全ではありません。
Jasmijn

12

TL; DR:フィルタリング用のGET、検索用のPOST

コレクションのリストからの結果のフィルタリングと複雑な検索を区別します。私が使用するリトマステストは、基本的にフィルタリング(ポジティブ、ネガティブ、または範囲)以上のものが必要な場合、POSTを必要とするより複雑な検索だと考えています。

これは、何が返されるかを考えるときに強化される傾向があります。通常、リソースのライフサイクルがほぼ完全な場合(PUT、DELETE、GET、コレクションGET)にのみGETを使用します。通常、コレクションGETでは、そのコレクションを構成するRESTリソースであるURIのリストを返します。複雑なクエリでは、応答を構築するために複数のリソースからプルすることがあり(SQL結合を考えてください)、URIではなく実際のデータを送り返します。問題は、データがリソースで表されないため、常にデータを返す必要があることです。これは、POSTを必要とする明確なケースのようです。

-

しばらく経ち、元の投稿は少しずさんだったので、更新するつもりでした。

GETは、ほとんどの種類のデータ、RESTリソースのコレクション、リソースの構造化データ、単一のペイロード(画像、ドキュメントなど)を返すための直感的な選択です。

POSTは、GET、PUT、DELETEなどに収まらないように思われるものすべてのキャッチオールメソッドです。

この時点で、単純な検索、フィルタリングはGETを通じて直感的に意味をなすと思います。複雑な検索は、特に集約関数、相互相関(結合)、再フォーマッターなどを投入する場合、個人の好み次第です。 )は、POSTリクエストの本文としてより意味があります。

また、API使用のエクスペリエンスの側面も考慮します。私は通常、ほとんどの方法をできるだけ使いやすく、できるだけ直感的にしたいと考えています。特に同じAPIの他のRESTリソースの動作と矛盾する場合は、より柔軟な(したがってより複雑な)呼び出しをPOSTおよび異なるリソースURIにプッシュします。

いずれにせよ、GETで検索するのかPOSTで検索するのかよりも、おそらく一貫性が重要です。

お役に立てれば。


1
RESTは、基礎となる実装を抽象化することを目的としているため(たとえば、リソースは必ずしもデータベース内の行またはハードドライブ上のファイルである必要はありませんが、何でもかまいません)、POST over SQL結合の実行に関してはGET。学校のテーブルと子供のテーブルがあり、クラス(1つの学校、複数の子供)が必要だとします。仮想リソースとを簡単に定義できますGET /class?queryParams。ユーザーの観点からは、「クラス」は常に重要であり、奇妙なSQL結合を行う必要はありませんでした。
-stevendesu

「フィルタリング」と「検索」の間に違いはありません。
ニコラスシャンクス

1
はい、あります。フィルターは既存のフィールドに基づいています。検索はadjecent値等を計算し、フィールドを組み合わせ、より複雑なパターンを含んでいてもよい
user13796を

@stevendesuまさに、だからこそ、両方にPOSTを使用する(検索を作成する):-)
ymajoros

@ymajoros検索用語と検索結果をどこかに保存していない限り、POSTが意味的に意味があることはわかりません。情報を要求している検索を実行するとき、どこにも保持される新しい情報を提供していません。
stevendesu

10

RESTでは、リソース定義は非常に広範囲です。実際には、いくつかのデータをバンドルする必要があります。

  • 検索リソースをコレクションリソースと考えると便利です。クエリパラメータ(URIの検索可能部分とも呼ばれる)は、クライアントが関心のあるアイテムにリソースを絞り込みます。

たとえば、メインのGoogle URIは、「インターネット上のすべてのサイトへのリンク」というコレクションリソースを指します。クエリパラメータは、表示するサイトに絞り込みます。

(URI =ユニバーサルリソース識別子。URL=ユニバーサルリソースロケーター。おなじみの "http://"はURIのデフォルト形式です。URLはロケーターですが、RESTではリソース識別子に一般化することをお勧めします。 。しかし、人々はそれらを交換可能に使用します。)

  • この例で検索しているリソースはジョブコレクションであるため、次のように検索するのが理にかなっています

GET site / jobs?type = blah&location = here&etc = etc

(リターン){ジョブ:[{ジョブ:...}]}

次に、POSTを使用します。これは、追加または処理の動詞であり、そのコレクションに新しいアイテムを追加します。

POSTサイト/ジョブ

{ジョブ:...}

  • job各ケースでオブジェクトの構造は同じであることに注意してください。クライアントは、クエリパラメーターを使用して検索を絞り込んでジョブのコレクションを取得し、アイテムの1つに同じ形式を使用して新しいジョブをPOSTできます。または、それらのアイテムの1つを取り、そのURIにPUTしてそのアイテムを更新できます。

  • 本当に長い、または複雑なクエリ文字列の場合、慣例により、代わりにそれらをPOSTリクエストとして送信しても問題ありません。クエリパラメータを名前/値のペア、またはJSONまたはXML構造のネストされたオブジェクトとしてバンドルし、リクエストの本文で送信します。たとえば、クエリに多数の名前/値ペアの代わりにネストされたデータがある場合。POSTのHTTP仕様では、追加動詞またはプロセス動詞として記述されています。(RESTの抜け穴から戦艦を航海する場合は、POSTを使用します。)

ただし、これをフォールバックプランとして使用します。

あなたがそれを行うときに失うものはa)GETは無効です-つまり、何も変更しません-POSTはそうではありません。そのため、呼び出しが失敗した場合、ミドルウェアは結果を自動的に再試行したりキャッシュしたりしません。2)本体の検索パラメーターを使用すると、URIをカットアンドペーストできなくなります。つまり、URIは必要な検索の特定の識別子ではありません。

「作成」と「検索」を区別するため。RESTのプラクティスと一致するオプションがいくつかあります。

  • ジョブの代わりにジョブ検索のように、コレクションの名前に何かを追加することで、URIでそれを行うことができます。つまり、検索コレクションを別個のリソースとして扱っているということです。

  • POSTのセマンティクスは両方とも追加ORプロセスであるため、検索ボディをペイロードで識別することができます。{job:...} vs. {search:...}のように。適切に投稿または処理するのはPOSTロジック次第です。

それはほとんど設計/実装の好みです。明確な慣習があるとは思わない。

したがって、既にレイアウトしたように、アイデアは次のコレクションリソースを定義することです。 jobs

サイト/ジョブ

GET +クエリパラメーターで検索して、検索を絞り込みます。長いまたは構造化されたデータクエリは、POSTの本体に(おそらく別の検索コレクションに)入ります。コレクションに追加するPOSTで作成します。そして、特定のURIへのPUTで更新します。

(FWIW URIのスタイル規則では、すべて小文字でハイフンで区切られた単語を使用します。しかし、そうする必要はありません。)

(また、あなたの質問から、あなたがこの道をずっと進んでいるのは明らかだと言うべきです。それらを並べるためだけに、ある種のことを明確に綴りましたが、あなたの質問はこの中のセマンティック問題のほとんどにすでに対処していました答え。私は単に慣習と練習を組み合わせていただけです。)


それは興味深いアイデアです-ペイロードを使用して区別することは考えていませんでした。ほとんど手に負えないようです!しかし、URIスキームには実際には動詞が含まれていないと思います。動詞を定義するのは要求タイプです。たぶん、ペイロードはURIよりも意味的にリクエストタイプに近いでしょう。唯一の懸念は-APIのユーザーに対して透過的ですか?
ロブベイリー14年

実装に関して(NodeとExpressを使用しています)、route処理の選択を実際に処理できないことを意味する場合があります。私はそれを見てみる必要があるだろう...
ロブ・ベイリー

私は同じ直感を持っていますが、URIで区切るときれいに見えます。私は一種の行き来です。それは判断の呼び出しです。ただし、HTTPのセマンティクスにより、HTTPを本文に含めることができます。RESTはWorld Wide Webをモデルにしており、WWWはGETとPOSTで構築されています。
ロブ14年

8

私は通常、ODataクエリを使用します。これらはGET呼び出しとして動作しますが、返されるプロパティを制限し、フィルタリングすることができます。

あなたはこのようなトークンを使用$select=して$filter=ますので、次のようになりますURIで終わるだろう。

/users?$select=Id,Name$filter=endswith(Name, 'Smith')

$skipand $topおよびordering を使用してページングを行うこともできます。

詳細については、OData.orgをご覧ください。使用している言語を指定していませんが、ASP.NETの場合、WebApiプラットフォームはODataクエリをサポートします-他(PHPなど)の場合、データベースクエリに変換するために使用できるライブラリが存在する可能性があります。


6
興味深いリンクであり、見る価値はありますが、GETリクエストはクエリ文字列で2000文字を超えてサポートされておらず、クエリこれよりはるかに長くなる可能性があるという説明されている基本的な問題を解決しますか?
ロブベイリー14年

@RobBaillieクエリ文字列を使用したGET呼び出しであるため、そうは思いません。ODataはWebデータソースのクエリの標準であるため、可能な限りODataを使用することをお勧めします。また、クエリは非常に複雑なため(2000文字のクエリに収まらない場合があるため)、特定のGET呼び出しを行うエンドポイント
Trevor Pilley 14年

「GET呼び出しを行う特定のエンドポイント」に対するアプローチを説明できますか?そのエンドポイントがどのように見えるか想像できますか?
ロブベイリー14年

@RobBaillie sure-あなたが使用している技術はわかりませんが、ASP.NETでは特定のコントローラーを作成するJobsNearMeAddedInTheLast7Daysか、ODataにとって長すぎる/複雑なクエリをカプセル化し、GET呼び出しでのみ公開します。
トレバーピリー14年

1
そうですか。私はこれが私の特定のケースで役立つだろうわからないものの、おそらく、いくつかの足を持っているもう一つの興味深い考えは-ファセットタイプの多くと可能なファセット値の多くで検索ファセット
ロブ・ベイリー

5

考慮すべき1つのアプローチは、可能なクエリのセットをコレクションリソースとして扱うことです/jobs/filters

POST本体にクエリパラメータを指定したこのリソースへのリクエストは、新しいリソースを作成するか、既存の同等のフィルターを識別し、そのIDを含むURLを返します/jobs/filters/12345

このIDは、ジョブのGETリクエストで使用できます/jobs?filter=12345GETフィルターリソースに対する後続のリクエストは、フィルターの定義を返します。

このアプローチには、フィルター定義のクエリパラメーター形式から解放されるという利点があり、潜在的に複雑なフィルターを定義するための強力な機能を提供します。OR条件は、クエリ文字列で達成するのが難しいと考えることができる1つの例です。

この方法の欠点は、URLの可読性が失われることです(ただしGET、フィルターリソースの要求を通じて定義を取得することで軽減できます)。このため/jobs、フィルターリソースをサポートするのと同じまたはリソースのクエリパラメーターのサブセットをサポートすることもできます。これは、短いクエリに使用できます。この機能が提供されている場合、上のクエリパラメータを使用する場合、フィルタリングの2種類のキャッシュ可能性を維持するために/jobsリソース、実装は、内部/作成フィルタリソースを再利用し、返すべき302か、303の形のURLを示すステータス/jobs?filter=12345


これに対する私の最初の反応は、それは良い情報ではあるが、@ burninggrammaによって提供される答えのバリエーションに過ぎないということです。基本的には、「フィルター/検索という新しいエンティティを作成し、呼び出して作成してから呼び出して取得する」です。バリエーションは、それを取得する呼び出しがコレクションに適用する呼び出しに似ているということです。面白い。しかし、あなたとburninggrammaの答えの両方が同じ問題に苦しんでいます-私はフィルターを作成したくない。それらは膨大な数になりますが、RESTful実装を維持する場合を除いて、保存する必要はありません。
ロブベイリー14年

2
クエリパラメーターが最適なソリューションであることは明らかですが、特定のサーバーによって課されるURLの制限よりも長いフィルター定義をどのように処理するかについての質問が特にあります。長さの制限を回避するには、何らかの方法でクエリ文字列を圧縮するか、任意の長さの本体の指定をサポートするリクエストメソッドを使用する必要があります。フィルターをリソースとして扱いたくない場合は、フィルター定義がPOSTされる非レストフルインターフェイスをサポートしてください。キャッシュ可能性は失われますが、データが十分に不安定な場合は、とにかくキャッシュしてもメリットはありません。
pgraham

フィルタを保存する必要はなく、単に保存するだけで解決できます。RESTが永続的であることを保証するものはありません。GET /jobs/37結果をリクエストして受信すると、誰かがリソースを削除し、2秒後に同じリクエストが404を返しPOST /searchesます。同様に、あなたとあなたが検索結果にリダイレクトされた場合(検索が作成され、リソースへの場所ヘッダー)、2秒後、その結果はメモリから消去され、再生成する必要があります。長期保管の必要はありません。
-stevendesu

5

これは古い答えですが、私はまだ議論に少し貢献できます。私は、REST、RESTful、およびアーキテクチャの誤解を非常に頻繁に観察しました。RESTfulは、検索を構築しないことについて何も言及していません。RESTfulにはアーキテクチャに関するものは何もありません。これは一連の設計原則または基準です。

より良い方法で検索を説明するには、特にアーキテクチャについて話さなければなりません。より適切なものはリソース指向アーキテクチャ(ROA)です。

RESTfulには設計の原則があります。dem等とは、いくつかの答えを読んで結果が変わらないという意味ではなく、独立したリクエストの結果は実行回数に依存しないことを意味します。それは変化する可能性があります.RESTful APIによって提供されるいくつかのデータを供給しているデータベースを継続的に更新していることを想像してみましょう。同じGETを実行すると結果が変わる可能性がありますが、実行回数に依存しません 世界をフリーズできれば、別の結果につながるリソースをリクエストしたときに、サービス内に状態、変換、何かが存在しないことを意味します。

定義上、リソースはそれ自体が物として参照されることが重要なものです。

リソース指向のアーキテクチャ(簡潔にするために、これからROAと呼びましょう)では、多くの可能性があるリソースに焦点を当てます。

  • ドキュメントのバージョン
  • ドキュメントの最終更新バージョン
  • 検索の結果
  • オブジェクトのリスト
  • 電子商取引から最初に購入した記事

リソースの点でユニークなのは追加性です。つまり、URI1つしかないということです。

そのようにして、検索はROAを考慮してRESTfulに完全に適合します。検索は通常の検索であり、何も変更しないと仮定しているため、GETを使用する必要があります。したがって、so等です(追加された新しい要素に応じて異なるものを返す場合でも)。ROAではなくRESTfulに固執する可能性があるため、ここでは混乱があります。つまり、ROAのアドレス可能性の原則を使用していないため、検索を作成し、同じパラメーターで異なるものを返すパターンに従うことができることを意味します。どう?さて、本文またはヘッダーで検索フィルターを送信すると、リソースはADDRESSABLEではありません。

W3の元のドキュメントには、正確な内容とURIの原則が記載されています。

https://www.w3.org/DesignIssues/Axioms

このアーキテクチャのURLはすべて自己記述的でなければなりません。原則に従ってURIのすべてに対処する必要があります。つまり、/(スラッシュ)を使用して必要なものを分離したり、パラメーターをクエリしたりすることができます。これには制限があることはわかっていますが、これはアーキテクチャパターンです。

RESTfulのROAパターンに従うと、検索は他のどのリソースよりも多くなく、唯一の違いは、リソースがオブジェクト自体との直接的な関係ではなく計算から得られることです。原則に基づいて、次のパターンに基づいた簡単な算術計算サービスに取り組み、それを取得できました。

http://myapi.com/sum/1/2

sum、1および2は変更できますが、計算の結果は一意であり、アドレス指定可能です。同じパラメーターを使用して呼び出すたびに、サービスで同じ変更を取得します。リソース/ sum / 1/2および/ substract / 5/4は、原則に完全に準拠しています。


3

1つのURIに対して常に同じ結果(表現)を返す静的コレクションがある場合、GETは問題ありません。また、これらの表現を生成するデータが変更されることはありません。ソースは読み取り専用データベースです。

GETが1つの同じURIに対して異なる結果を返すと、べき等性/安全性CoolURI原則に違反するため、RESTfulではありません。べき等動詞をデータベースに書き込むことは可能ですが、表現に影響を与えてはなりません。

一般的な検索は、結果への参照を返すPOSTリクエストで始まります。結果を生成します(新しく、後続のGETで取得できます)。もちろん、この結果は階層的(GET可能なURIのさらなる参照)である可能性があり、アプリケーションにとって意味がある場合は、以前の検索の要素を再利用する可能性があります。

ちなみに、私は人々が違うやり方をしていることを知っています。RESTに違反することがいかに便利であるかを説明する必要はありません。


Aaaaaaaah-だから、それがどのように動作するはずです!ありがとう!
ロブベイリー

1
dem等性とは、常に正確に同じ値を返す必要があるという意味ではなく、NOTHINGが変更された場合に同じ値を返す必要があります。検索は計算の結果と考えることができ、それ自体がリソースです。
マキシミリアーノリオス

べき等は実際に結果が同じままであることを意味します。キャッシュ制御を使用することは可能ですし、実行可能です。そしてもちろん、後のGETを妨げるDELETEを使用できます。ただし、エージェントがアプリケーションの内部動作に関する知識を保持する必要がある場合、RESTfulではなくなります。上記で、私はRESTの最も極端なアイデアについて話していました。実際には、人々はそれの多くの側面に違反することができます。キャッシュがもはや効率的にキャッシュしない場合、彼らは代価を払っています。
マーティンスジオアルト

「I等とは、結果が同じままであることを意味します。」...リクエスト後。ポイントは、リクエストがデータを変更しないということです。
AndreiMotinga
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.