RESTfulな検索/フィルタリングを設計する方法は?[閉まっている]


457

現在、PHPでRESTful APIを設計および実装しています。しかし、私は最初の設計の実装に失敗しています。

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

これまでのところ、かなり標準的ですよね?

私の問題は最初のものGET /usersです。リストをフィルタリングするために、リクエスト本文でパラメーターを送信することを検討していました。これは、次のように、非常に長いURLを取得せずに複雑なフィルターを指定できるようにするためです。

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

代わりに、私は次のようなものを望んでいました:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

これはもっと読みやすく、複雑なフィルターを設定する大きな可能性を与えてくれます。

とにかく、file_get_contents('php://input')リクエストのリクエスト本文を返しませんでしたGET。私も試しましたhttp_get_request_body()が、使用している共有ホスティングにはありませんpecl_http。とにかくそれが役に立ったかどうかはわかりません。

私はこの質問を見つけ、GETはおそらくリクエストボディを持つべきではないことに気づきました。それは少し決定的ではありませんでしたが、彼らはそれに対して助言しました。

だから今私は何をすべきかわかりません。RESTfulな検索/フィルタリング機能をどのように設計しますか?

私はを使用できると思いますがPOST、それはあまりRESTfulに見えません。



60
注意してください!!!GETメソッドはIDEMPOTENTであり、「キャッシュ可能」である必要があります。本文で情報を送信する場合システムはどのようにリクエストをキャッシュできますか?HTTPでは、リクエストの本文ではなく、URLのみを使用してGETリクエストをキャッシュできます。たとえば、この2つのリクエスト: example.com {test: "some"} example.com {anotherTest: "some2"}は、キャッシュシステムによって同じであると見なされます。どちらも同じURLを持っています
jfcorugedo

15
追加するには、/ user(シングルユーザー)ではなく、/ users(コレクション)にPOSTする必要があります。
Mladen B.

1
考慮すべきもう1つのポイントは、ほとんどのアプリサーバーにはURLをログに記録するアクセスログがあり、その間に何かがある可能性があることです。そのため、GETで意図しない情報リークが発生する可能性があります。
user3206144 2018

回答:


397

RESTful検索を実装する最良の方法は、検索自体をリソースと見なすことです。その後、検索を作成しているため、POST動詞を使用できます。POSTを使用するために、データベースに文字通り何かを作成する必要はありません。

例えば:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

ユーザーの観点から検索を作成しています。これの実装の詳細は無関係です。一部のRESTful APIは永続性さえ必要としない場合があります。これは実装の詳細です。


209
検索エンドポイントにPOSTリクエストを使用する際の重要な制限の1つは、ブックマークを付けることができないことです。検索結果(特に複雑なクエリ)のブックマークは非常に便利です。
カウシャント2014年

73
POSTを使用して検索を行うと、RESTキャッシュの制約が壊れる可能性があります。whatisrest.com/rest_constraints/cache_excerps
フィリップ

56
検索は、その性質上、一時的なものです。データは、同じパラメーターを使用する2つの検索間で展開されるため、GET要求は検索パターンにきれいにマップされないと思います。代わりに、検索リクエストはPOST(/ Resource / search)である必要があり、その検索を保存して検索結果にリダイレクトできます(例:/ Resource / search / iyn3zrt)。このように、GETリクエストは成功し、理にかなっています。
sleblanc 2014

32
投稿は検索に適した方法ではないと思います。通常のGETリクエストのデータも時間とともに変化する可能性があります。
不思議

82
これは絶対に考えられる最悪の答えです。あまりにも多くの賛成票があるとは思えません。この答えは、理由を説明する:programmers.stackexchange.com/questions/233164/...を
リチャード

141

GETシステムでURLのみを使用するため、GETリクエストをキャッシュできないため、GETリクエストでリクエスト本文を使用すると、RESTの原則に違反します。

さらに悪いことに、ユーザーをこのページにリダイレクトするために必要なすべての情報がURLに含まれていないため、URLをブックマークできません。

リクエストボディパラメータの代わりにURLまたはクエリパラメータを使用します。

例えば:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

実際、HTTP RFC 7231は次のように述べています。

GETリクエストメッセージ内のペイロードには、セマンティクスが定義されていません。GETリクエストでペイロードボディを送信すると、一部の既存の実装がリクエストを拒否する可能性があります。

詳細については、こちらをご覧ください


29
私の間違いから学ぶ-受け入れられた回答の提案(jsonのPOST)を使用してAPIを設計しましたが、URLパラメーターに移動しています。ブックマーク機能は、あなたが考えるよりも重要かもしれません。私の場合、トラフィックを特定の検索クエリ(広告キャンペーン)に転送する必要がありました。また、履歴APIを使用することは、URLパラメータでより意味があります。
ジェイク

2
それはその使い方に依存します。これらのパラメーターに基づいてページをロードするURLにリンクしている場合、それは理にかなっていますが、メインページがフィルターパラメーターに基づいてデータを取得するためだけにAJAX呼び出しを行っている場合は、とにかくそれをブックマークできません。 ajax呼び出しとは関係ありません。当然のことながら、そこに行くとフィルターを構築し、それをajax呼び出しにPOSTするURLをブックマークすることもできます。
Daniel Lorenz

@DanielLorenz最高のユーザーエクスペリエンスを実現するには、その場合でもHistory APIを使用してURLを変更する必要があります。ブラウザーの戻る機能を使用して前のページに移動することがWebサイトで許可されていないときは、我慢できません。標準のサーバー側で生成されたページの場合、ブックマーク可能にする唯一の方法はGETリクエストを使用することです。古き良きクエリパラメータが最善の解決策のようです。
Nathan

@ネイサン私はこの答えを誤解していると思います。getでクエリ文字列パラメーターを使用することについて話していました。GET呼び出しでは本体パラメーターを使用しないでください。完全に役に立たないからです。私はクエリ文字列を使用したGETについてさらに話していましたが、ページの起動時にこれらのパラメーターを使用してPOSTへのフィルターを構築し、これらのパラメーターを使用してデータを取得できます。そのシナリオでは、履歴は引き続き正常に機能します。
ダニエルロレンツ

@DanielLorenzああ、わかりました。あなたの言っていることを誤解したと思います。
ネイサン、

70

リソースのフィルタリング/検索はRESTfulな方法で実装できるようです。アイデアは、/filters/またはと呼ばれる新しいエンドポイントを導入すること/api/filters/です。

このエンドポイントフィルターの使用はリソースと見なすことができるため、POSTメソッドを介して作成されます。この方法-もちろん-bodyを使用してすべてのパラメーターを運ぶことができ、複雑な検索/フィルター構造を作成できます。

このようなフィルターを作成した後、検索/フィルター結果を取得するには2つの方法があります。

  1. 一意のIDを持つ新しいリソースが201 Createdステータスコードと共に返されます。次に、このIDを使用して、GET要求を次の/api/users/ように行うことができます。

    GET /api/users/?filterId=1234-abcd
    
  2. 新しいフィルターが作成された後、POSTそれは応答しません201 Createdが、同時にヘッダーが303 SeeOtherとともに応答します。このリダイレクトは、基盤となるライブラリを介して自動的に処理されます。Location/api/users/?filterId=1234-abcd

どちらのシナリオでも、フィルターされた結果を取得するには2つの要求を行う必要があります。これは、特にモバイルアプリケーションの場合、欠点と見なされる場合があります。モバイルアプリケーションの場合、への単一POST呼び出しを使用し/api/users/filter/ます。

作成したフィルターを保持する方法は?

それらはDBに保存して後で使用できます。これらは、redisなどの一時的なストレージに保存することもでき、有効期限が切れると削除されるTTLがあります。

このアイデアの利点は何ですか?

フィルター、フィルターされた結果はキャッシュ可能で、ブックマークすることもできます。


2
よくこれは受け入れられた答えであるはずです。RESTの原則に違反していないため、リソースに対して複雑で長いクエリを実行できます。それは素晴らしく、きれいで、ブックマーク互換です。唯一の追加の欠点は、作成されたフィルターのキーと値のペアを保存する必要があることと、すでに述べた2つの要求ステップです。
dantebarba

2
このアプローチの唯一の問題は、クエリに日付時刻フィルター(または常に変化する値)がある場合です。次に、db(またはキャッシュ)に保存するフィルターの数は無数です。
Rvy Pandey

17

リクエストパラメータを使用する必要があると思いますが、やりたいことを実行するための適切なHTTPヘッダーがない場合に限ります。HTTPの仕様では、明示的にGETが身体を持つことができないことを、言っていません。ただし、このペーパーでは次のように述べています。

慣例により、GETメソッドが使用される場合、リソースを識別するために必要なすべての情報はURIにエンコードされます。HTTP / 1.1には、クライアントがURIのクエリ部分ではなくHTTPエンティティ本体でデータをサーバーに提供する安全な対話(検索など)に関する規則はありません。つまり、安全な操作のために、URIが長くなる可能性があります。


6
ElasticSearchは、ボディでGETも行い、うまく機能します!
Tarun Sapra

ええ、しかし彼らはサーバーの実装を制御しているのですが、インターウェブ上のxaseではないかもしれません。
user432024 2017

7

私はlaravel / phpバックエンドを使用しているので、次のようなものを使用する傾向があります。

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHPは自動的に[]paramsを配列に変換するため、この例では$filter、フィルターの配列/オブジェクトを保持する変数に加えて、ページと、積極的にロードしたい関連リソースがあります。

別の言語を使用する場合でも、これは適切な規則である可能性があり[]、配列に変換するパーサーを作成できます。


このアプローチは、素敵に見えますが、URLで角括弧を使用しての問題があるかもしれません、見何-文字-CAN-1-使用・イン・-URL
スカイ

2
@Skyこれは、[およびをURIエンコードすることで回避できます]。これらの文字のエンコードされた表現を使用してクエリパラメータをグループ化することは、よく知られている方法です。JSON:API仕様でも使用されます
ジェルハン

6

最初のAPIが完全にRESTfulであるかどうかにかかわらず、あまり心配しないでください(特にアルファ段階にいる場合)。最初にバックエンド配管を機能させます。あらゆる種類のURL変換/書き換えをいつでも行うことができます。広範囲にわたるテスト( "ベータ版")に十分な安定性が得られるまで繰り返し調整を行います。

パラメータがURI自体の位置と規則によってエンコードされ、常に何かにマッピングすることがわかっているパスを前に付けたURIを定義できます。私はPHPを知りませんが、そのような機能が(Webフレームワークを使用する他の言語に存在するように)存在すると想定します。

.ie。ストア#1でparam [i] = value [i] for i = 1..4を使用して「ユーザー」タイプの検索を実行します(URIクエリパラメータの省略形としてvalue1、value2、value3、...):

1) GET /store1/search/user/value1,value2,value3,value4

または

2) GET /store1/search/user,value1,value2,value3,value4

または次のように(私はそれをお勧めしませんが、後で詳しく説明します)

3) GET /search/store1,user,value1,value2,value3,value4

オプション1では、接頭辞が付いたすべてのURIを/store1/search/userデフォルトで検索ハンドラー(またはPHPの指定)にマップし、store1(と同等)の下のリソースを検索し/search?location=store1&type=userます。

APIによって文書化および適用される慣例により、パラメーター値1〜4はコンマで区切られ、その順序で示されます。

オプション2は、検索タイプ(この場合はuser)を位置パラメーター#1として追加します。どちらのオプションも単なる表面的な選択です。

オプション3も可能ですが、希望しないと思います。特定のリソース内での検索機能は、検索自体の前にあるURI自体で提示する必要があると思います(URIで検索がリソース内で特定であることを明確に示しているかのように)。

これがURIのパラメーターを渡すことの利点は、検索がURIの一部であることです(したがって、検索はリソース、つまり内容が時間とともに変化し、変化するリソースとして扱われます)。欠点は、パラメーターの順序が必須であることです。 。

このようなことをしたら、GETを使用できます。これは読み取り専用リソースになります(POSTまたはPUTできないため、GETされると更新されます)。それは、呼び出されたときにのみ存在するようになるリソースでもあります。

また、結果を一定期間キャッシュするか、DELETEを使用してキャッシュを削除することで、セマンティクスをさらに追加することもできます。ただし、これは、ユーザーが通常DELETEを使用する目的に反する場合があります(通常、ユーザーはキャッシュヘッダーを使用してキャッシュを制御するためです)。

どのようにそれを行うかは設計上の決定ですが、これが私のやり方です。これは完璧ではなく、これを行うことが最善の方法ではない場合があると確信しています(特に非常に複雑な検索条件の場合)。


7
ヨ、あなた(誰か、誰でも/何でも)が私の答えに賛成投票することに賛成するなら、少なくともあなたが正確に何に同意しないかを示すコメントを書くことを自我に傷つけますか?私はそれがinterweebzであることを知っていますが、...;)
luis.espinal

107
私は反対票を投じなかったが、質問は「私は現在、RESTful APIを設計および実装しています」で始まり、「最初のAPIが完全にRESTfulであるかどうかにかかわらず、あまり心配しないでください」という答えで始まるという事実は、私には間違っています。APIを設計している場合は、APIを設計しています。問題は、APIを設計する必要があるかどうかではなく、APIを最適に設計する方法を尋ねることです。
gardarh 2012年

14
API システムであり、最初にAPIで動作し、バックエンドの配管ではありません。最初の実装は単なるモックである可能性があります。HTTPにはパラメーターを渡すためのメカニズムがあり、再発明することをお勧めしますが、さらに悪い(名前と値のペアの代わりにパラメーターを順序付ける)。したがって、反対票。
スティーブンヘロデ2013

14
@gardarh-はい、それは間違っていると感じますが、時には実用的です。主な目的は、手元のビジネスコンテキストで機能するAPIを設計することです。完全にRESTFULLのアプローチが現在のビジネスに適している場合は、それを検討してください。そうでない場合は、それをしないでください。つまり、特定のビジネス要件を満たすAPIを設計します。主要な要件としてRESTfullにしようとすることは、「X / Y問題でアダプターパターンをどのように使用するか」と尋ねることと大差ありません。彼らが実際の価値ある問題を解決しない限り、ホーンのパラダイムを踏まないでください。
luis.espinal 2013

1
私はリソースをいくつかの状態のコレクションと見なし、パラメーターをその状態の表現をパラメーターで操作する手段と見なしています。このように考えてください。ノブとスイッチを使用してリソースの表示方法を調整できる場合(リソースの特定のビットを表示/非表示にしたり、順序を変えたりなど)、これらのコントロールはパラメーターです。それが実際に別のリソース(たとえば、「/ albums」と「/ artists」)の場合、それはパスで表す必要があるときです。とにかく、それが私には直感的です。
エリックエリオット

2

参考までに:これは少し遅れていることを知っていますが、興味のある人にとっては。どのくらいRESTfulになりたいかによって異なりますが、HTTP仕様ではあまり明確ではないため、独自のフィルタリング戦略を実装する必要があります。すべてのフィルターパラメーターをURLエンコードすることをお勧めします。

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

私はそれが醜いことを知っていますが、それはそれを行うための最もRESTfulな方法であり、サーバー側で簡単に解析できるはずだと思います:)

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