RESTネストリソースのベストプラクティスは何ですか?


301

私の知る限り、個々のリソースには正規パスが1つだけ必要です。次の例では、適切なURLパターンは何でしょうか?

例として、会社の残りの表現を取り上げます。この架空の例では、各会社 0以上の部門を所有し、各部門 0以上の従業員を所有しています。

関連する会社なければ、部門は存在できません

従業員、関連する部門なしでは存在できません

これで、リソースパターンの自然な表現がわかります。

  • /companies 会社のコレクション -新しい会社のプットを受け入れます。コレクション全体を手に入れよう。
  • /companies/{companyId}個々の会社。GET、PUT、DELETEを受け入れる
  • /companies/{companyId}/departments新しいアイテムのPOSTを受け入れます。(社内に部門を作成します。)
  • /companies/{companyId}/departments/{departmentId}/
  • /companies/{companyId}/departments/{departmentId}/employees
  • /companies/{companyId}/departments/{departmentId}/employees/{empId}

制約を考えると、各セクションでは、少し深く入れ子にするとこれは理にかなっていると感じます。

ただし、GETすべての会社のすべての従業員をリスト()にしたい場合、問題が生じます。

そのためのリソースパターンは、/employees(全従業員のコレクション)に最も密接にマッピングされます。

それは、/employees/{empId}もしそうなら、同じリソースを取得するための2つのURIがあるので、私も持っている必要があるということですか?

または、スキーマ全体をフラット化する必要があるかもしれませんが、それは従業員がネストされたトップレベルのオブジェクトであることを意味します。

基本レベルで/employees/?company={companyId}&department={deptId}は、最も深くネストされたパターンとまったく同じ従業員のビューが返されます。

リソースは他のリソースによって所有されている、個別にクエリ可能でなければならないURLパターンのベストプラクティスは何ですか?


1
これは、ほぼ正確に記載されたものとoppsite問題であるstackoverflow.com/questions/7104578/...回答は関連するかもしれないが。どちらの質問も所有権に関するものですが、この例は最上位のオブジェクトが所有者ではないことを示しています。
2014年

1
まさに私が疑問に思っていたもの。与えられたユースケースでは、ソリューションは問題ないように見えますが、リレーションがコンポジションではなく集約の場合はどうでしょうか。ここでもベストプラクティスを理解するのに苦労しています...また、このソリューションは関係の作成のみを意味しますか。たとえば、既存の人物が雇用されているか、人物オブジェクトが作成されていますか?
Jakob O. 14年

それは私の架空の例で人を作成します。私がこれらのドメイン用語を使用した理由は、私の実際の問題を模倣していますが、その合理的に理解できる例です。リンク関係の質問に目を通しましたか。
ウェス14

質問を回答と質問に分けました。
2015

回答:


152

あなたがしたことは正しいです。一般に、同じリソースに対して多くのURIが存在する可能性があります。これを行うべきではないというルールはありません。

そして一般的に、アイテムに直接または他のサブセットとしてアクセスする必要があるかもしれません-そのため、あなたの構造は私にとって意味があります。

従業員が部門の下でアクセスできるからといって:

company/{companyid}/department/{departmentid}/employees

会社の下でもアクセスできないという意味ではありません。

company/{companyid}/employees

その会社の従業員を返すでしょう。それはあなたの消費するクライアントが何を必要としているのかに依存します-それはあなたが設計すべきものです。

しかし、すべてのURLハンドラーが同じバッキングコードを使用してリクエストを満たし、コードが重複しないようにしたいと思います。


11
これは、RESTfulの精神を指摘しています。意味のあるリソースを最初に検討する場合にのみ、すべきかすべきでないというルールはありません。しかし、さらに、そのようなシナリオでコード複製ないためのベストプラクティスは何でしょうか。
abookyun

13
@abookyun両方のルートが必要な場合、それらの間で繰り返されるコントローラーコードをサービスオブジェクトに抽象化できます。
bgcode

これはRESTとは関係ありません。RESTは、URLのパス部分をどのように構造化するかについては気にしません...それが気にするすべては有効で、できれば永続的なURIです...
redben

この答えを駆使して、動的セグメントがすべて一意の識別子であるAPIは、複数の動的セグメントを処理する必要はないはずだと思います(/company/3/department/2/employees/1)。APIが各リソースを取得する方法を提供する場合、それらの各要求の作成は、クライアント側のライブラリで、またはコードを再利用する1回限りのエンドポイントとして行うことができます。
最大

1
禁止事項はありませんが、リソースへのパスを1つだけにする方がエレガントで、すべてのメンタルモデルをシンプルに保つことができます。また、入れ子になっている場合でも、URIがリソースタイプを変更しないようにしたいと思います。たとえば/company/*、会社のリソースのみを返し、リソースの種類はまったく変更しないでください。これはRESTによって指定されていません。一般的に指定が不十分ですが、個人的な好みにすぎません。
kashif 2017年

174

ネストされたエンドポイントとネストされていないエンドポイントの両方の設計戦略を試しました。私はそれを見つけました:

  1. ネストされたリソースに主キーがあり、親の主キーがない場合、システムが実際にそれを必要としない場合でも、ネストされた構造はそれを取得する必要があります。

  2. ネストされたエンドポイントには、通常、冗長なエンドポイントが必要です。つまり、部門全体の従業員のリストを取得できるように、多くの場合、追加の/ employeesエンドポイントが必要になります。/ employeesがある場合、/ companies / departments / employeesは何を購入するのですか?

  3. ネストされたエンドポイントはそれほどうまく進化しません。たとえば、今は従業員を検索する必要はないかもしれませんが、後で検索する必要があり、ネストされた構造がある場合は、別のエンドポイントを追加するしかありません。ネストされていないデザインでは、パラメーターを追加するだけで済みます。

  4. リソースには、複数のタイプの親が含まれる場合があります。その結果、複数のエンドポイントがすべて同じリソースを返します。

  5. 冗長なエンドポイントがあると、ドキュメントの記述が難しくなり、APIの学習も難しくなります。

つまり、ネストされていない設計では、より柔軟で単純なエンドポイントスキーマを使用できるようです。


24
この答えに出会えてとても新鮮でした。「正しい方法」であると教えられた後、ネストされたエンドポイントを数か月間使用しています。私はあなたが上に挙げた同じ結論のすべてに行きました。ネストされていないデザインを使用すると、はるかに簡単になります。
user3344977

6
あなたはマイナス面のいくつかをプラス面として挙げているようです。「より多くのパラメーターを単一のエンドポイントに詰め込むだけ」では、APIの文書化と学習が難しくなり、その逆は起こりません。;-)
ドレンミ2017年

4
この回答のファンではありません。ネストされたリソースを追加したからといって、冗長なエンドポイントを導入する必要はありません。ネストされたリソースを本当の所有者が所有している場合は、複数の親から同じリソースが返されても問題ありません。ネストされたリソースと対話する方法を学ぶために親リソースを取得することは問題ではありません。良い発見可能なREST APIはこれを行うべきです。
Scottm

3
@Scottm-私が遭遇したネストされたリソースの1つの欠点は、親リソースIDが正しくない/不一致である場合、誤ったデータを返す可能性があることです。承認の問題がないと仮定すると、ネストされたリソースが実際に渡された親リソースの子であることを確認するのは、API実装に任されています。このチェックがコーディングされていない場合、API応答が正しくないために破損する可能性があります。あなたの考えは何ですか?
アンディDufresne

1
エンドリソースすべてに一意のIDがある場合、中間の親IDは必要ありません。たとえば、IDで従業員を取得するには、GET / companies / departments / employees / {empId}を取得するか、会社123のすべての従業員を取得するには、GET / companies / 123 / departments / employees /を取得します。中間リソースにアクセスして、フィルター/作成/変更を行うことができ、私の意見では発見可能性に役立ちます。
PaulG

77

私が行ったことを質問から、より多くの人々がそれを見る可能性が高い回答に移動しました。

私がやったことは、ネストされたエンドポイントに作成エンドポイントを設定することです。アイテムを変更またはクエリするための正規のエンドポイントは、ネストされたリソースにありません

したがって、この例では(リソースを変更するエンドポイントをリストするだけです)

  • POST /companies/ 新しい会社を作成すると、作成した会社へのリンクが返されます。
  • POST /companies/{companyId}/departments 部門が配置されると、新しい部門が作成するリンクが /departments/{departmentId}
  • PUT /departments/{departmentId} 部門を変更します
  • POST /departments/{deparmentId}/employees 新しい従業員を作成し、へのリンクを返します /employees/{employeeId}

したがって、各コレクションにはルートレベルのリソースがあります。ただし、作成所有オブジェクト内にあります。


4
同じタイプのデザインも思いついた。このような「所属する場所」を作成するのは直感的ですが、それでもグローバルにリストすることはできます。リソースが親を持つ必要がある関係がある場合はさらにそうです。次に、そのリソースをグローバルに作成しても、それは明白ではありませんが、このようにサブリソースで作成することは完全に理にかなっています。
Joakim 2016

私はあなたがPOST意味を使用したと思いますPUT、そしてそれ以外の場合。
ヘラルド・リマ

実際には、この場合のサーバーは(リンク内の)IDを返すので、事前に割り当てられたIDを使用して作成していないことに注意してください。したがって、POSTの記述は正しいです(同じ実装でgetを実行することはできません)。ただし、プットはリソース全体を変更しますが、同じ場所で引き続き使用できるため、PUTします。PUTとPOSTは別の問題であり、議論の余地もあります。たとえば、stackoverflow.com
Wes

@Wes親の下に動詞メソッドを変更することを好みます。しかし、グローバルリソースのクエリパラメータを渡すことは適切に受け入れられますか?例:クエリパラメータがcompany = company-idのPOST / departments
Ayyappa 2018

1
@Mohamad理解と制約の適用の両方で他の方法の方が簡単だと思う場合は、遠慮なく答えてください。この場合は、マッピングを明示的にします。それはパラメーターで動作する可能性がありますが、実際にはそれが問題です。最善の方法は何ですか。
Wes

35

上記の回答はすべて読みましたが、共通の戦略がないようです。Microsoft DocumentsのDesign APIのベストプラクティスに関する良い記事を見つけました。参考にすべきだと思います。

より複雑なシステムでは、クライアントがいくつかのレベルの関係をナビゲートできるようにするURIを提供するのは魅力/customers/1/orders/99/products.的です。代わりに、URIを比較的シンプルに保つようにしてください。アプリケーションがリソースへの参照を取得すると、この参照を使用して、そのリソースに関連するアイテムを検索できるようになります。上記のクエリをURI /customers/1/ordersに置き換えて、顧客1のすべての注文/orders/99/productsを検索してから、この注文で製品を検索できます。

ヒント

よりも複雑なリソースURIを要求しないようにします collection/item/collection


3
あなたが与える参照は、複雑なURIを作成しないことで際立っている点とともに驚くべきものです。
vicco

したがって、ユーザーのチームを作成する場合、POST / teams(ユーザーのユーザーID)またはPOST / users /:id / teams
coinhndp

@coinhndpこんにちは、POST / teamsを使用する必要があります。アクセストークンを承認すると、userIdを取得できます。つまり、承認コードが必要なものを作成するときに、そうですか?どのフレームワークを使用しているかはわかりませんが、APIコントローラでuserIdを取得できると思います。例:ASP.NET APIでは、ApiControllerのメソッド内からRequestContext.Principalを呼び出します。Spring Secirityでは、SecurityContextHolder.getContext()。getAuthentication()。getPrincipal()が役立ちます。AWS NodeJS Lambdaでは、ヘッダーオブジェクトのcognito:usernameです。
Long Nguyen

POST / users /:id / teamsの何が問題になっていますか。上記で投稿したMicrosoftドキュメントで推奨されていると思います
coinhndp '16

@coinhndpあなたが管理者としてチームを作成する場合、それは良いことです。しかし、通常のユーザーとして、なぜパスにuserIdが必要なのかわかりません。user_Aとuser_Bがあるとします。user_AがPOST / users / user_B / teamsを呼び出した場合、user_Aがuser_Bの新しいチームを作成できるとしたらどうでしょう。したがって、この場合はuserIdを渡す必要はありません。userIdは認証後に取得できます。しかし、teams /:id / projectsは、例えばチームとプロジェクトの関係を作成するのに適しています。
Long Nguyen

10

URLの外観はRESTとは関係ありません。何でもあり。これは実際には「実装の詳細」です。変数に名前を付ける方法と同じです。彼らがしなければならないすべては、ユニークで耐久性があります。

これに多くの時間を無駄にしないでください。選択を行い、それに固執する/一貫性を保つだけです。たとえば、階層を使用する場合、すべてのリソースに対してそれを行います。クエリパラメータを使用する場合...コード内の命名規則と同じように。

なんでそうなの ?私が知る限り、「RESTful」APIはブラウズ可能です(ご存知のように...「アプリケーション状態のエンジンとしてのハイパーメディア」)。したがって、APIクライアントは、URLがどのようなものであるかを気にしません有効(デバッグ用の場合を除いて、これらの「フレンドリーなURL」を読み取る必要のあるSEOや人間は存在しません...)

コード内の変数の名前がそうであるように、REST APIでのURLの素晴らしさや理解しやすさは、APIクライアントではなく、API開発者としてのみ興味があります。

最も重要なことは、APIクライアントがメディアタイプを解釈する方法を知っていることです。たとえば、それはそれを知っています:

  • メディアタイプには、使用可能な/関連するリンクをリストするリンクプロパティがあります。
  • 各リンクは関係によって識別されます(ブラウザがlink [rel = "stylesheet"]がそのスタイルシートを意味するか、rel = favicoがファビコンへのリンクであることを知っているように...)
  • そして、それらの関係の意味がわかっています(「会社」は会社の​​リストを意味します。「検索」はリソースのリストを検索するためのテンプレート化されたURLを意味します。「部署」は現在のリソースの部門を意味します)

以下はHTTP交換の例です(記述しやすいため、ボディはyamlにあります)。

リクエスト

GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml

応答:メインリソースへのリンクのリスト(会社、人、何でも...)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: this is your API's entrypoint (like a homepage)  
links:
  # could be some random path https://api.acme.local/modskmklmkdsml
  # the only thing the API client cares about is the key (or rel) "companies"
  companies: https://api.acme.local/companies
  people: https://api.acme.local/people

リクエスト:企業へのリンク(以前のレスポンスのbody.links.companiesを使用)

GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml

応答:(アイテムの下の)会社の部分的なリスト。リソースには、次の2つの会社を取得するためのリンク(body.links.next)、検索のための他の(テンプレート化された)リンク(body.links.search)などの関連リンクが含まれます。

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: representation of a list of companies
links:
  # link to the next page
  next: https://api.acme.local/companies?page=2
  # templated link for search
  search: https://api.acme.local/companies?query={query} 
# you could provide available actions related to this resource
actions:
  add:
    href: https://api.acme.local/companies
    method: POST
items:
  - name: company1
    links:
      self: https://api.acme.local/companies/8er13eo
      # and here is the link to departments
      # again the client only cares about the key department
      department: https://api.acme.local/companies/8er13eo/departments
  - name: company2
    links:
      self: https://api.acme.local/companies/9r13d4l
      # or could be in some other location ! 
      department: https://api2.acme.local/departments?company=8er13eo

したがって、リンク/リレーションの方法を確認すると、URLのパス部分を構造化する方法は、APIクライアントにとって何の価値もありません。また、URLの構造をドキュメントとしてクライアントに伝えている場合は、RESTを実行していません(少なくとも、「リチャードソンの成熟度モデルによるレベル3ではありません)。


7
「REST APIでのURLの素晴らしさや理解しやすさは、コード内の変数の名前がそうであるように、APIクライアントではなく、API開発者としてのみ興味があります。」なぜこれが面白くないのですか?自分以外の誰かがAPIを使用している場合、これは非常に重要です。これはユーザーエクスペリエンスの一部であるため、APIクライアント開発者にとって理解しやすいことが非常に重要です。リソースを明確にリンクすることで物事をさらに理解しやすくすることはもちろんボーナスです(提供するURLのレベル3)。すべては、直感的かつ論理的で、明確な関係を持つ必要があります。
Joakim 2016

1
@Joakimレベル3のREST API(Hypertext As The Engine Of Application State)を作成している場合、URLのパス構造は(有効である限り)クライアントにはまったく関係ありません。レベル3を目指していない場合は、はい、それは重要であり、推測できるはずです。しかし、実際のRESTはレベル3です。良い記事:martinfowler.com/articles/richardsonMaturityModel.html
redben

4
私は、人間にとってユーザーフレンドリーではないAPIまたはUIを作成することに反対しています。レベル3かどうかにかかわらず、リソースをリンクすることは素晴らしいアイデアだと思います。しかし、そうすることを提案することは、「URLスキームを変更することを可能にする」ことは、現実、および人々がAPIをどのように使用するかには触れないことです。だから、それは悪い勧告です。しかし、すべての世界で最高の状態であれば、誰もがレベル3 RESTになるでしょう。私はハイパーリンクを組み込み、人間が理解できるURLスキームを使用しています。レベル3は前者を除外するものではなく、私の意見では1つは注意すべきです。でも良い記事です:)
Joakim 2016

もちろん、保守性やその他の懸念のために注意する必要があります。私はあなたが私の答えのポイントを逃していると思います:URLの見た目は多くの考えに値しないので、「ただ選択してそれに固執する必要があります」一貫して」と答えた。そして、REST APIの場合、少なくとも私の意見では、ユーザーフレンドリーはURLにはなく、主に(メディアタイプ)にあります。とにかく、私のポイントを理解していただければ幸いです:)
redben

9

私はこの種の道に同意しません

GET /companies/{companyId}/departments

部門を取得したい場合は、/ departmentsリソースを使用することをお勧めします

GET /departments?companyId=123

私はあなたがcompaniesテーブルとテーブルを持っていると思います、そしてdepartmentsあなたが使うプログラミング言語でそれらをマップするためのクラスがあります。また、部署は会社以外のエンティティに接続できると想定しているため、/ departmentsリソースは簡単で、リソースをテーブルにマッピングしておくと便利です。また、再利用できるため、エンドポイントの数が少なくて済みます。

GET /departments?companyId=123

たとえば、あらゆる種類の検索

GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.

部門を作成する場合は、

POST /departments

リソースを使用し、リクエストの本文に会社IDを含める必要があります(部門を1つの会社にのみリンクできる場合)。


1
私にとって、これは、ネストされたオブジェクトがアトミックオブジェクトとして理にかなっている場合にのみ許容できるアプローチです。そうでない場合、それらを分解することは本当に意味がありません。
Simme、2016

これは、部門も取得できるようにする場合、つまり/ departmentsエンドポイントを使用する場合に、私が言ったことです。
Maxime Laval 2016

2
GET /companies/{companyId}?include=departments会社とその部門の両方を単一のHTTPリクエストで取得できるため、会社を取得するときに部門を遅延読み込みで含めることもできます。フラクタルはこれを本当にうまくやっています。
Matthew Daly 2017

1
ACLを設定しているときは、/departmentsエンドポイントを管理者のみがアクセスできるように制限し、各企業が `/ companies / {companyId} / departments`を介して自分の部門にのみアクセスできるようにする必要があります
Cuzox

@MatthewDaly ODataも$ expandでうまく機能します
Rob Grant

1

Railsはこれに対するソリューションを提供します:浅いネスト

ここで他の回答で説明されているように、既知のリソースを直接扱う場合、ネストされたルートを使用する必要がないため、これは良いことだと思います。

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