URI対クエリ文字列によるREST APIの設計


73

次のような3つのリソースがあるとします。

Grandparent (collection) -> Parent (collection) -> and Child (collection)

上記は、これらのリソース間の関係を示しています。各祖父母は、1つまたは複数の親にマップできます。各親は、1つまたは複数の子にマップできます。子リソースに対する検索をサポートする機能が必要ですが、フィルター条件があります。

クライアントが祖父母へのID参照を渡した場合、その祖父母の直接の子孫である子供に対してのみ検索したいです。

クライアントが親へのID参照を渡した場合、親の直接の子孫である子に対してのみ検索したいです。

私はそのようなことを考えました:

GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}

そして

GET /myservice/api/v1/parents/{parentID}/children?search={text}

上記の要件に対して、それぞれ。

しかし、私はこのようなこともできます:

GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}

この設計では、クライアントにクエリ文字列のいずれかを渡すことができます:grandparentIDまたはparentIDのいずれかで、両方ではありません。

私の質問は:

1)どのAPI設計がよりRESTfulであり、その理由は何ですか?意味的には、同じ意味であり、同じように動作します。URIの最後のリソースは「子」であり、事実上、クライアントが子リソースを操作していることを意味します。

2)クライアントの観点からの理解可能性、およびデザイナーの観点からの保守性という点で、それぞれの長所と短所は何ですか。

3)リソースの「フィルタリング」以外に、実際に使用されるクエリ文字列は何ですか?最初の方法を使用する場合、フィルターパラメーターは、クエリ文字列パラメーターではなく、パスパラメーターとしてURI自体に埋め込まれます。

ありがとう!


3
あなたの質問のタイトルは、これを見ている人にとって非常に紛らわしいはずです。URIの有効なセグメントは、<scheme>:// <user>:<password> @ <host>:<port> / <path>; <params>?<query> /#<fragment>(< password>は非推奨)「クエリ文字列」はURIの有効なコンポーネントなので、タイトルの「vs」はおかしな話です。
K.

という意味I want to only search against children who are INdirect descendants of that grandparent.ですか?あなたの構造によると、祖父母には直接の子供はいません。
nullの

子供と親の違いは何ですか?彼は子供を持っていない場合、親は親ですか?設計違反の
臭い

re:potential design flawそして、あなたが人についての情報を持っているが、両親についての情報を持っていない場合、彼らは資格がありchildますか?(例、アダムとイブ):)
ジェシーチザム

回答:


64

最初

RFC 3986§3.4(ユニフォームリソース識別子§(構文コンポーネント)|クエリ

3.4クエリ

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

クエリコンポーネントは、非階層データの取得用です。家系図よりも本質的に階層的なものはほとんどありません!Ergo- 「REST-y」であるかどうかに関係なく、インターネット上のシステムの開発、フォーマット、プロトコル、フレームワークに準拠するために、この情報を識別するためにクエリ文字列を使用しないでください。

RESTはこの定義とは関係ありません。

特定の質問に対処する前に、「検索」のクエリパラメータの名前が適切ではありません。より良いのは、クエリセグメントをキーと値のペアの辞書として扱うことです。

クエリ文字列は、より適切に定義できます

?first_name={firstName}&last_name={lastName}&birth_date={birthDate}

特定の質問に答える

1)どのAPI設計がよりRESTfulであり、その理由は何ですか?意味的には、同じ意味であり、同じように動作します。URIの最後のリソースは「子」であり、事実上、クライアントが子リソースを操作していることを意味します。

これは、あなたが信じているほど明確ではないと思います。

これらのリソースインターフェイスはRESTfulではありません。RESTfulアーキテクチャスタイルの主な前提条件は、アプリケーション状態の遷移をハイパーメディアとしてサーバーから伝達する必要があることです。人々はURIの構造を何とか「RESTful URI」にするために労力を費やしましたが、RESTに関する公式の文献には実際にはこれについてほとんど語ることができません。私個人の意見では、RESTに関するメタ誤情報の多くは、古くて悪い習慣を打破する目的で発行されたものです。(真に「RESTful」なシステムを構築するのは、実際にはかなり手間がかかります。業界は「REST」に夢中になり、無意味な資格と制限でいくつかの直交的な懸念を埋めました。

RESTの文献には、アプリケーションプロトコルとしてHTTPを使用する場合、プロトコルの仕様の正式な要件を遵守する必要があり、「httpを作成しながら、httpを使用していることを宣言する」ことはできないと書かれています。 ; リソースを識別するためにURIを使用する場合は、URI / URLに関する仕様の正式な要件に従う必要があります。

あなたの質問は、RFC3986§3.4で直接対処されています。この問題の一番下の行は、APIを「RESTful」と見なすにはURIが不十分であるにもかかわらず、システムを実際「RESTful」にしたい場合、HTTPとURIを使用している場合、クエリ文字列:

3.4クエリ

クエリコンポーネントに非階層データが含まれています

...それは簡単です。

2)クライアントの観点からの理解可能性、およびデザイナーの観点からの保守性という点で、それぞれの長所と短所は何ですか。

最初の2つの「長所」は、正しい道にいるということです。3番目の「短所」は、完全に間違っているように見えることです。

理解可能性と保守性に関する限り、これらは間違いなく主観的であり、クライアント開発者の理解レベルと設計者の設計チョップに依存します。URI仕様は、URIのフォーマット方法に関する決定的な答えです。階層データは、パス上およびパスパラメーターで表されることになっています。非階層データはクエリで表現されることになっています。このセマンティクスは、要求されている表現のメディアタイプに特に依存するため、フラグメントはより複雑です。したがって、あなたの質問の「理解しやすい」コンポーネントに対処するために、最初の2つのURIが実際に言っていることを正確に翻訳しようとします。次に、有効なURIで達成しようとしているとあなたが言うことを表現しようとします。

逐語的なURIの意味的意味への翻訳 /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text} これは、祖父母の両親search={text} にとって、祖父母の兄弟を検索する場合にのみ、あなたのURIであなたが言ったことを首尾一貫している子供を見つけます。「祖父母、両親、子供」と「祖父母」が親に世代を上げ、そして親の子供を見ることによって「祖父母」世代に戻ったことを見つけた。

/myservice/api/v1/parents/{parentID}/children?search={text} これは、{parentID}で識別される親について、?search={text}これが望んでいるものに近い方の子を見つけ、API全体をモデル化するために使用できる親->子の関係を表していることを示しています。このようにモデル化するために、クライアントに「grandparentId」がある場合、所有するIDと表示したいファミリーグラフの部分の間に間接層があることを認識する負担がかかります。「grandparentId」で「子」を見つけるには、/parents/{parentID}/childrenサービスを呼び出し、返された子をforeachして、子を検索して個人識別子を取得します。

URIとしての要件の実装 ツリーをたどることができる、より拡張可能なリソース識別子をモデル化する場合、それを実現するいくつかの方法が考えられます。

1)最初のものは、すでに言及しました。「人」のグラフを複合構造として表します。各人は、その親のパスを介してその上の世代への参照を持ち、その子のパスを介してその下の世代を参照します。

/Persons/Joe/Parents/Mother/Parents ジョーの母方の祖父母をつかむ方法です。

/Persons/Joe/Parents/Parents ジョーの祖父母のすべてをつかむ方法です。

/Persons/Joe/Parents/Parents?id={Joe.GrandparentID} あなたが持っている識別子を持つジョーの祖父母をつかむでしょう。

これらはすべて理にかなっています(「Parents / Parents / Parents」パターンのブランチIDがないため、サーバーでdfsを強制することでタスクに応じてパフォーマンスが低下する可能性があることに注意してください)。任意の世代の世代をサポートする機能。何らかの理由で8世代を検索したい場合、これを次のように表すことができます。

/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}

しかし、これにより、このデータを表すための2番目の主要なオプション、つまりパスパラメーターが使用されます。


2)パスパラメータを使用して「階層を照会」する次の構造を開発して、消費者の負担を軽減し、なおかつ意味のあるAPIを使用できます。

147世代を振り返って、このリソース識別子をパスパラメーターで表すと、次のことが可能になります。

/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}

JoeをGreat Grandparentから見つけるには、Joe's Idの既知の世代数をグラフで確認します。 /Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}

これらのアプローチで注目すべき重要な点は、識別子とリクエストにさらなる情報がなければ、最初のURIがJoe.NotableAncestorの識別子でJoeから147世代後のPersonを取得していることを期待することです。2番目のものがJoeを取得することを期待する必要があります。実際に必要なのは、呼び出し元のクライアントがノードのセット全体と、ルートPersonとURIの最終コンテキストとの関係を取得できることです。同じURI(装飾を追加)text/vnd.graphvizでこれを実行し、リクエストでAcceptを設定できます。これは、.dotグラフ表現のIANA登録メディアタイプです。それで、URIを

/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor.Id}#directed

HTTPリクエストヘッダー Accept: text/vnd.graphviz を使用すると、147番目の先祖世代にJoeの「注目すべき祖先」として識別される人物が含まれる場合、Joeと147世代の間の世代階層の有向グラフが必要であることをクライアントにかなり明確に伝えることができます。

text / vnd.graphvizがそのフラグメントに対して事前定義されたセマンティクスを持っているかどうかはわかりません。命令の検索で何も見つかりませんでした。そのメディアタイプに実際に事前定義されたフラグメント情報がある場合は、そのセマンティクスに従って、準拠するURIを作成する必要があります。ただし、これらのセマンティクスが事前に定義されていない場合、URI仕様では、フラグメント識別子のセマンティクスは制約を受けず、代わりにサーバーによって定義されるため、この使用法は有効です。


3)リソースの「フィルタリング」以外に、実際に使用されるクエリ文字列は何ですか?最初の方法を使用する場合、フィルターパラメーターは、クエリ文字列パラメーターではなく、パスパラメーターとしてURI自体に埋め込まれます。

私はすでにこれを徹底的に打ち負かしたと思いますが、クエリ文字列はリソースを「フィルタリング」するためのものではありません。これらは、非階層データからリソースを識別するためのものです。あなたが行くことによって、あなたのパスを指定して、階層をドリルダウンしている場合は /person/{id}/children/、あなたが識別するために希望されている特定の子供や、特定の子供のセットを、あなたが識別し、クエリ内に含まれているセットに適用されるいくつかの属性を使用します。


1
RFCは、相対的なURI参照を解決するための構文とアルゴリズムを定義している限り、階層のみに関係します。元の投稿の例が適合していない理由を説明するいくつかの情報源を詳しく説明したり引用したりできますか?
user2313838

2
家系図は実際にはグラフではなく、ツリーではなく、階層的でもありません。複数の親、離婚、再婚などを考慮しています
。– Myster

1
@RobertoAloi HTTPの定義がすでにある場合、空のセットを介して独自の「アイテムが見つかりません」インターフェースを通信するのは直感に反するようです。基本的な原則は、サーバーに「もの」を返すように要求することであり、返す「もの」がない場合、サーバーはそれを「404-見つかりません」と通信します。
K.

1
404は、リソースのルートURL、つまりコレクション全体が見つからなかったことを示すと常に信じていました。したがって、/ books?author = Jimを照会し、jimによる書籍がなかった場合、空のセット[]を受け取ります。しかし、/ articles?author = Jimを照会したが、記事リソースがコレクションとしても存在しなかった場合404は、記事をまったく検索することに役に立たないことを示すのに役立ちます。
18年

1
@adjenksあなたは正式な仕様からそれを学びませんでした。構造的には、URLの構成要素は、構成要素の目的を判断するのに役立つ場合、「ルート」を含むと考えることができますが、クエリ文字列は、パスを介して識別されるリソースに対する表示フィルターではありません。クエリ文字列は、URLのファーストクラスの市民です。URL(クエリ文字列を含む)に一致するリソースがサーバー上にない場合、404はこれを伝えるためにhttp内で定義された手段です。ポーズを設定した相互作用モデルは、違いのない区別をもたらします。
K.

14

これはあなたが間違っているところです:

クライアントがID参照を渡した場合

RESTシステムでは、クライアントがIDに煩わされることはありません。クライアントが知る必要がある唯一のリソース識別子はURIでなければなりません。これが「均一インターフェース」の原理です。

クライアントがシステムとどのように対話するかを考えてください。ユーザーが祖父母のリストを閲覧し始めたとすると、彼は祖父母の子供の1人を選んだので、彼をに連れて行き/grandparent/123ます。クライアントがの子を検索できる場合、/grandparent/123「HATEOAS」に従って、クエリを実行したときに返されるものはすべて/grandparent/123、検索インターフェイスにURLを返す必要があります。このURLには、現在の祖父母によるフィルタリングに必要なデータが埋め込まれているはずです。

リンクは次のようになりますかどう/grandparent/123?search={term}か、/parent?grandparent=123&search={term}または/parent?grandparentTerm=someterm&someothergplocator=blah&search={term}RESTによると取るに足らないです。これらのURLのすべてが同じ数のパラメーターを持っていることに注目してください{term}。これらのURLを切り替えることも、特定の祖父母に応じてそれらを混在させることもできます。基盤となる実装が大きく異なる場合でも、リソース間の論理的な関係は同じであるため、クライアントは壊れません。

代わりに、/grandparent/{grandparentID}?search={term}一方に行くときは必要だが/children?parent={parentID}&search={term}、もう一方に行くときはa} が必要になるようにサービスを作成した場合、クライアントは概念的に類似するさまざまな関係でさまざまなものを補間する必要があるため、カップリングが多すぎます。

実際に使用するの/grandparent/123?search={term}か、それとも/parent?grandparent=123&search={term}好みの問題であるのか、そして現在の方がどちらの実装がより簡単であるのか。重要なことは、URL戦略を変更した場合、または異なる親子関係で異なる戦略を使用する場合、クライアントの変更を要求しないことです。


10

URLにID値を入れることは、どういうわけかREST APIであり、RESTは動詞を処理し、リソースを渡すことを意味すると考える理由がわかりません。

したがって、新しいユーザーをPUTしたい場合は、かなりの量のデータを送信する必要があり、POST httpリクエストが理想的ですので、キー(たとえば、ユーザーID)を送信しても、ユーザーデータを送信します(名前、住所など)POSTデータとして。

現在では、リソース識別子をURIに入れるのが一般的なイディオムですが、これは、「URIにない場合はRESTではない」正規の形式よりも規約です。REST の元の論文では、httpについてはまったく言及していないことに注意してください。これ、クライアントサーバーでデータを処理するイディオムであり、httpの拡張ではありません(ただし、明らかに、httpはRESTを実装する主要な形式です) 。

たとえば、フィールディングは例として学術論文のリクエストを使用します。リソース「ビールの飲み方に関するジョン博士の論文」を取得したいが、初期バージョンまたは最新バージョンが必要な場合があるため、リソース識別子は単一のIDとして簡単に参照できるものではない可能性があるURIに配置されます。RESTはこれを可能にし、その背後にある意図は次のとおりです。

代わりに、RESTは、識別される概念の性質に最適なリソース識別子を選択する作成者に依存します

そのため、静的URIを使用して親を取得し、クエリ文字列で検索語を渡して、追跡している正確なユーザーを識別することを妨げるものは何もありません。この場合、識別している「リソース」は祖父母のセットです(したがって、URIにはURIの一部として「祖父母」が含まれます。RESTには、どの表現を決定するために設計された「制御データ」の概念が含まれますリソースが取得されます-これらに示されている例はキャッシュ制御ですが、バージョン管理でもあります-ジョン博士の優れた論文に対する私の要求は、URIの一部ではなく、制御データとしてバージョンを渡すことで改善できます。

通常言及されないRESTインターフェースの例はSMTPだと思います。メールメッセージを作成するときは、メールメッセージの各部分のリソースデータとともに動詞(FROM、TOなど)を送信します。これはhttp動詞を使用せず、独自のセットを使用しますが、RESTfulです。

したがって... URIに何らかのリソースIDが必要な場合でも、ID参照である必要はありません。これは、クエリ文字列またはPOSTデータで制御データとして送信できます。REST APIで実際に識別しているのは、URIにすでにある子を追跡しているということです。

だから私の考えでは、RESTの定義を読んで、子リソースを要求し、制御データを(クエリ文字列の形式で)渡して、どれを返すかを決定します。その結果、祖父母または親に対してURI要求を行うことはできません。あなたは欲しい子供が用語親/祖父母やidは間違いなく、そのようなURIであってはならないので、返されました。子供はそうあるべきです。


実際、概念としてのURIはRESTの中心です。ただし、URI構文はそれほど重要ではありません。適切なRESTインターフェースは、共通の構文でリソースを識別する必要があります。RESTの原則の根底では、RFC 3986 URIの代わりにJSONオブジェクトで複雑なリソースを識別することは必ずしも悪いことではありませんが、そうする場合は、API全体にJSON RIを使用する必要があります。
ライライアン

4

多くの人が、RESTの意味などについて既に話し合っています。しかし、実際の問題、つまり設計に対処している人はいないようです。

なぜ祖父母は父親と違うのですか?彼らは両方ともおそらく子供を持つことができる子供を持っています...

最終的に、それらはすべて「人間」です。あなたはおそらくあなたのコードにもそれを持っています。それを使用します:

GET /<api-endpoint>/humans/<ID>

人間に関するいくつかの有用な情報を返します。(名前、およびもの)

GET /<api-endpoint>/humans/<ID>/children

明らかに子の配列を返します。子が存在しない場合は、おそらく空の配列を使用する方法です。

簡単にするために、たとえばフラグ「hasChildren」を追加できます。または「hasGrandChildren」。

難しくなく賢く考える


1

以下は、すべてのgrandparentIDが独自のURLを取得するため、よりRESTfullです。このようにして、リソースは一意の方法で識別されます。

GET /myservice/api/v1/grandparents/{grandparentID}
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}

クエリパラメータ検索は、そのリソースのコンテキストから検索を実行するのに適した方法です。

ファミリが非常に大きくなっている場合、たとえば、クエリオプションとしてstart / limitを使用できます。

GET /myservice/api/v1/grandparents/{grandparentID}/children?start=1&limit=50

開発者として、一意のURL / URIを持つさまざまなリソースを用意することをお勧めします。クエリパラメータも使用できない場合にのみ使用する必要があると思います。

たぶんこれは良い読み物ですhttp://www.thoughtworks.com/insights/blog/rest-api-design-resource-modelingそしてそうでなければRoy T Fieldingの元の博士論文https://www.ics.uci.edu /~fielding/pubs/dissertation/fielding_dissertation.pdfは、概念を非常によく完全に説明しています。


1

私は一緒に行きます

/myservice/api/v1/people/{personID}/descendants/2?search={text}

この場合、すべてのプレフィックスは有効なリソースです。

  • /myservice/api/v1/people:すべての人
  • /myservice/api/v1/people/{personID}:完全な情報(祖先、兄弟、子孫を含む)を持つ1人
  • /myservice/api/v1/people/{personID}/descendants:一人の子孫
  • /myservice/api/v1/people/{personID}/descendants/2:一人の孫

最良の答えではないかもしれませんが、少なくとも私には理にかなっています。


-1

私の前の多くは、URL形式がRESTfulサービスのコンテキストでは重要ではないことをすでに指摘しています...私の2セントは...元の記事で提唱されたRESTの重要な側面は「リソース」の概念でした。 URLを設計する際に留意する必要がある可能性のある側面の1つは、「リソース」は単一のテーブルの行ではないことです(可能性はあります)。むしろ、表現であり、一貫性もあります。 .andを使用して、その表現の状態を変更することができます(そして事実上、ストレージの基礎となる媒体に)。そして、特定のリソースはビジネスコンテキストでのみ意味があります。

あなたの例では/ myservice / api / v1 / grandparents / {grandparentID} / parents / children?search = {text}

/ myservice / api / v1 / siblingsOfGrandParents?search =。を短縮すると、より意味のあるリソースになる可能性があります。

この場合、「siblingsOfGrandParents」をリソースとして宣言できます。これにより、URLが簡素化されます。

他の人が指摘したように、ドメインオブジェクト間のあらゆる種類の階層関係にURLでより明示的な形で収まる必要があるという誤解された概念が広くあります。ドメインオブジェクト間の1対1マッピングとリソースとすべての関係を表します...質問はむしろ信じるべきです... URLを介してそのような関係を明らかにする必要があります...具体的にはパスセグメント...おそらくそうではありません。


貴重な時間を費やして答えをダウングレードするとき、あなたが声を上げて理由を述べることができれば助かります。
セントゥーシヴァサンブ

あなたの答えを読んだことに基づいてなぜダウン投票されたのかは完全にはわかりません。仰るとおりです。私に飛びついたいくつかの直接的なことは、OPが階層関係について尋ねていたときに「兄弟」に言及することによって、少し接線的な例を提供したということです。おそらくあなたの文章スタイルも改善されるでしょう。多くの「...」を使用しますが、正式な、専門的な、または教育的な文章では「...」をあまり使用しない傾向があります。冗長性も少しあります(たとえば、あなたが例に言及してから言います)。おそらく、あなたの答えはより簡潔で、OPの質問により直接的に対応している可能性があります。
ケニーケーソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.