web-dev-qa-db-ja.com

REST APIをURIとクエリ文字列で設計する

次のように関連する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}

この設計では、クライアントがクエリ文字列でどちらか一方を渡せるようにすることができます。

私の質問は:

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

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

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

ありがとう!

76
HiChews123

最初

通り RFC 3986§3.4(Uniform Resource Identifiers§(Syntax Components)| Query

3.4クエリ

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

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

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

特定の質問に取り組む前に、 "search"のクエリパラメータの名前が不適切です。クエリセグメントをキーと値のペアのディクショナリとして扱うことをお勧めします。

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

?first_name={firstName}&last_name={lastName}&birth_date={birthDate}など.

特定の質問に答えるため

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

これはあなたが信じているようにはっきりしているとは思いません。

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

REST文献によると、アプリケーションプロトコルとしてHTTPを使用する場合は、プロトコルの仕様の正式な要件に準拠する必要があり、「実行中にhttpを作成することはできません。さらに、httpを使用していることを宣言します。リソースを識別するためにURIを使用する場合は、URI/URLに関する仕様の正式な要件に準拠する必要があります。

あなたの質問は、上記でリンクしたRFC3986§3.4によって直接扱われます。この問題の要点は、システムに実際にbe "RESTful"を要求し、HTTPとURIを使用している場合、準拠するURIはAPIを「RESTful」と見なすには不十分です。次の理由により、クエリ文字列では階層データを識別できません。

3.4クエリ

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

...それはそれと同じくらい簡単です。

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

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

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

逐語的URIの意味的意味への翻訳/myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}これは、祖父母の親に対して、search={text}を持つ子を見つけることを意味します祖父母の兄弟を検索する場合にのみ、URIで一貫性があると述べました。 「祖父母、両親、子供」と一緒に、「祖父母」が両親に1世代上に行き、次に両親の子供を見ると「祖父母」世代に戻ったことがわかりました。

/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}は、手元にある識別子を持っているJoeの祖父母を取得します。

これらはすべて意味があります(「親/親/親」パターンのブランチ識別がないため、サーバーに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}

祖父母からジョーを見つけるには、グラフのジョーのIDの既知の世代数を調べます。 /Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}

これらのアプローチの主な注意点は、識別子とリクエストに詳細情報がない場合、最初のURIがJoe.NotableAncestorの識別子を使用してJoeから147世代前のPersonを取得することです。 2つ目はJoeを取得するはずです。実際に必要なのは、呼び出し側クライアントがノードのセット全体と、ルートPersonとURIの最終コンテキストとの間のノードの関係を取得できるようにすることです。同じURIを使用して(いくつかの追加の装飾を使用して)、リクエストにAcceptのtext/vnd.graphvizを設定できます。これは、.dotグラフ表現のIANA登録メディアタイプです。それで、URIを

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

hTTPリクエストヘッダーAccept: text/vnd.graphvizを使用すると、ジョーと147世代の間の世代階層の有向グラフが必要であることをクライアントにかなり明確に伝えることができます。

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


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

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

66
K. Alan Bates

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

クライアントから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戦略を変更する場合、または異なる親子関係で異なる戦略を使用する場合に、クライアントを変更する必要がないことです。

14
Lie Ryan

なぜ人々がURLにID値を配置することが、どういうわけかREST AP​​I、RESTは動詞の処理、リソースの受け渡しについてであると考える理由がわかりません。

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

現在、URIにリソースIDを配置するのは一般的な慣用法ですが、これは、「URIにない場合、RESTでない」という標準的な形式よりも慣例です。 元の論文 of 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 AP​​Iで実際に識別しているのは、URIにすでに持っている子の後にいるということです。

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

10
gbjbaanb

多くの人がすでにRESTの意味などについて話しました。しかし、実際の問題、つまりあなたのデザインに対処している人はいません。

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

結局、それらはすべて「人間」です。あなたはおそらくあなたのコードにもそれを持っています。だからそれを使う:

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

人間に関する有用な情報を返します。 (名前など)

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

明らかに子の配列を返します。子が存在しない場合は、おそらく空の配列が適しています。

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

難しくなく賢く考える

4
Pinoniq

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

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

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

家族が非常に大きくなると、たとえば、クエリオプションとして開始/制限を使用できます。

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
l.renkema

一緒に行きます

/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:1人の子孫
  • /myservice/api/v1/people/{personID}/descendants/2:一人の孫

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

1
laike9m