web-dev-qa-db-ja.com

REST入れ子になったリソースのベストプラクティスは何ですか?

私が言うことができる限りでは、それぞれの個々のリソースはただ一つの正規パスを持つべきです。それで、以下の例で良い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}を持っているべきだということを意味していますか?

あるいは、スキーマ全体をフラットにする必要がありますが、それは従業員がネストされた最上位オブジェクトであることを意味します。

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

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


アップデート:私がしたことを見るために、下の私の答えを見てください。

250
Wes

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

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

従業員が部署の下でアクセス可能であるという理由だけで、

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

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

company/{companyid}/employees

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

しかし、私はあなたがコードを重複させないようにすべてのURLハンドラが要求を満たすために同じ裏付けコードを使うことを望みます。

128
jeremyh

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

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

  2. ネストしたエンドポイントは通常、冗長エンドポイントを必要とします。言い換えれば、部署間で従業員のリストを取得できるように、多くの場合、追加の/ employeesエンドポイントが必要になります。 /従業員がいる場合、/会社/部門/従業員は正確に何を購入していますか?

  3. ネスティングエンドポイントはそれほどうまく進化しません。例えば。今すぐ従業員を検索する必要はないかもしれませんが、後でまたネストされた構造がある場合は別のエンドポイントを追加する以外に選択肢はありません。ネストされていないデザインでは、パラメータを追加するだけです。これはより簡単です。

  4. リソースに複数の種類の親がある場合があります。結果として複数のエンドポイントがすべて同じリソースを返します。

  5. 冗長なエンドポイントはドキュメントを書くのを難しくし、またapiを学ぶのを難しくします。

つまり、ネストされていない設計では、より柔軟で簡単なエンドポイントスキーマが可能になります。

138
Patc

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

私がしたことは、ネストしたエンドポイントに作成エンドポイントを持つことです。アイテムを変更または照会するための正規のエンドポイントはではありません)です。入れ子のリソース

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

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

そのため、各コレクションにはルートレベルのリソースがあります。しかしcreate所有オブジェクトにあります。

66
Wes

私は上記の答えをすべて読んだが、共通の戦略を持っていないようだ。 Microsoft DocumentsのDesign APIのベストプラクティス に関する優れた記事を見つけました。私はあなたが参照すべきだと思います。

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

ヒント

collection/item/collectionよりも複雑なリソースURIを要求しないでください。

18
Long Nguyen

URLがどのように見えるかは、RESTとは関係ありません。何でもあり。それは実際には「実装の詳細」です。だからあなたがあなたの変数に名前を付けるのと同じように。彼らがしなければならないのはユニークで丈夫です。

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

なぜそうなのか ?私が知っている限りでは "RESTful"なAPIはブラウズ可能であるべきです(あなたは知っています... "アプリケーション状態のエンジンとしてのハイパーメディア")、したがってAPIクライアントはそれらがある限りあなたのURLがどんなものかを気にしません。有効(デバッグ用以外にSEOや人間のURLを読む必要のある人はいません)

URLがREST AP​​Iの中でどれだけ素敵でわかりやすいかは、APIクライアントではなく、API開発者としてのあなたにとってだけ興味深いものです。コードは。

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

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

以下はHTTP交換の例です(書くのが簡単なので本文が大丈夫です):

リクエスト

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

返答:会社の部分的なリスト(項目の下)、リソースには次の会社を取得するためのリンク(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をしていません(あるいは少なくとも "Level 3" Richardsonの成熟度モデル ")

8
redben

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

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つの会社のみにリンクできる場合)。

7
Maxime Laval

Railsはこれに対する解決策を提供します: 浅いネスト

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

1
partydrone