web-dev-qa-db-ja.com

REST APIデザイン:ネストされたコレクションと新しいルート

この質問は、最適なREST API設計と、ネストされたリソースとルートレベルのコレクションのどちらかを選択するために直面​​している問題についてです。

概念を示すために、コレクションCityBusiness、およびEmployeesがあるとします。典型的なAPIは次のように構築できます。 ABC、X7N、WWWがキーであると想像してください。 GUID:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/City/ABC/Businesses/X7N                   (returns business X7N)
GET Api/City/ABC/Businesses/X7N/Employees         (returns all employees at business X7N)
PUT Api/City/ABC/Businesses/X7N/Employees/WWW     (updates employee WWW)

これは、元のドメイン構造に従っているため、クリーンに見えます。ビジネスは都市にあり、従業員はビジネスにいます。個々のアイテムには、コレクションの下のキーを介してアクセスできます(たとえば、../Businessesはすべてのビジネスを返し、../Businesses/X7Nは個々のビジネスを返します)。

APIコンシューマーが実行できる必要があることは次のとおりです。

  • 都市でビジネスを獲得する(GET Api/City/ABC/Businesses)
  • すべての従業員をビジネスに参加させる(GET Api/City/ABC/Businesses/X7N/Employees)
  • 個々の従業員情報を更新する(PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

その2番目と3番目の呼び出しは、適切な場所にあるように見えますが、実際には不要な多くのパラメーターを使用します。

  • ビジネスで従業員を獲得するために必要な唯一のパラメーターは、ビジネスのキー(X7N)です。
  • 個々の従業員を更新するために必要な唯一のパラメーターは、従業員のキー(WWW)です。

バックエンドコードには、ビジネスを検索したり従業員を更新したりするために重要でない情報を必要とするものはありません。したがって、代わりに、次のエンドポイントの方が適切に表示されます。

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Businesses/X7N/Employees                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

ご覧のとおり、ドメインの観点からはサブ/サブサブコレクションですが、企業と従業員用に新しいrootを作成しました。

どちらの解決策も私にはあまりきれいに見えません。

  • 最初の例は不要な情報を要求しますが、消費者には「自然」に見えるように構成されています(コレクションからの個々のアイテムは下の葉から取得されます)
  • 2番目の例では、必要な情報のみを要求しますが、「自然な」方法で構造化されていません。サブコレクションにはルート経由でアクセスできます。
  • 新しい従業員を追加するとき、個々の従業員ルートは機能しません。これは、従業員を追加するビジネスを知る必要があるためです。つまり、コールは少なくともPOST Api/Businesses/X7N7/Employeesなどのビジネスルートの下に存在する必要があります。すべてがさらに混乱します。

私が考えていない、よりクリーンで3番目の方法はありますか?

32
Alex

RESTが、2つのリソースが同じ値を持つことができないという制約を追加する方法がわかりません。resourceType/IDは、最善の方法ではなく、最も簡単な使用例の例にすぎません。 RESTfulな観点から行ってください。

ロイ・フィールディングの論文の 段落5.2.1.1 を注意深く読むと、フィールディングがとを区別していることに気付くでしょう。 aresource。これで、リソースには一意のURIが必要になります。これは事実です。しかし、2つのリソースが同じ値を持つことを妨げるものは何もありません。

たとえば、アカデミックペーパーの「著者が好むバージョン」は、時間の経過とともに値が変化するマッピングですが、「会議Xの議事録に掲載されたペーパー」へのマッピングは静的です。これらは2つの異なるリソースですある時点で両方が同じ値にマップされている場合でも両方のリソースを識別できるように区別する必要があります独立して参照されます。ソフトウェアエンジニアリングの同様の例は、「最新のリビジョン」、「リビジョン番号1.2.7」、または「Orangeリリースに含まれるリビジョン」を参照するときにバージョン管理されたソースコードファイルを個別に識別することです。

したがって、あなたが言うように、ルートを変更することを妨げるものは何もありません。あなたの例では、Businessはリソースではなく値です。 「IDがxであるビジネス」リソースも持ちながら、「都市にあるすべてのビジネス」(ロイの例のように、「オレンジリリースに含まれるリビジョン」)のリストであるリソースを作成することは完全にRESTfulです。 (「改訂番号x」のように)。

Employeesの場合、企業とその従業員の関係は composition の関係であるため、API/Businesses/X7N/Employeesを維持します。したがって、あなたが言うように、EmployeesBusinessesクラスルートからのみアクセスする必要があります。しかし、これはREST要件ではなく、他の代替手段も完全にRESTfulです。


これはHATEAOSの原則の適用と対になっていることに注意してください。 APIでは、都市にあるビジネスのリストは、API/Businessesへのリンクのリストである可能性があります(おそらく理論的な観点からはそうあるべきです)。ただし、これは、クライアントがリスト内のアイテムごとにサーバーへのラウンドトリップを1回実行する必要があることを意味します。これは効率的ではなく、実用的であるために、私が行うことは、この例ではAPI/BusinessesとなるURIへのselfリンクとともにビジネスの表現をリストに埋め込むことです。

22
edsioufi

RESTを特定のURI命名規則の適用と混同しないでください。

リソースの名前の付け方は完全に二次的なものです。 HTTPリソースの命名規則を使用しようとしています。これはRESTとは関係ありません。ロイ・フィールディング自身は、他の人が上で引用した文書の中で繰り返しそう述べています。 RESTはプロトコルではなく、アーキテクチャスタイルです。

実際、ロイ・フィールディングは2008年のブログコメントで述べています( http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 6/20/2012) :

"A REST APIは、固定リソース名または階層(クライアントとサーバーの明らかな結合)を定義してはなりません。サーバーは、独自の名前を自由に制御できる必要があります。代わりに、サーバーがクライアントに指示できるようにしますメディアタイプとリンクリレーション内でこれらの命令を定義することにより、HTMLフォームやURIテンプレートで行われるような適切なURIを構築する方法。」

つまり、本質的には:

あなたが説明する問題は、実際にはRESTの問題ではありません。概念的には、階層構造とリレーショナル構造の問題です。

ビジネスは都市の「中に」あるので、都市の「階層」の一部と見なすことができます。75の都市にオフィスを持つ国際企業はどうでしょうか。その後、都市は突然、構造の上級レベルに商号を持つ階層のジュニア要素になります。

重要なのは、さまざまな角度からデータを表示できることです。視点によっては、データを階層として表示するのが最も簡単な場合があります。ただし、同じデータを異なるレベルの階層と見なすことができます。 HTTPタイプのリソース名を使用している場合は、HTTPで定義された階層構造を入力しています。これは制約です、はい、しかしそれはREST制約ではなく、HTTP制約です。

その角度から、シナリオにより適したソリューションを選択できます。顧客が会社名を入力するときに都市名を指定できない場合(知らない場合もあります)、都市名のみのキーを使用することをお勧めします。私が言ったように、それはあなた次第であり、RESTはあなたの邪魔になりません...

もっと要点:

GET PUTなどでHTTPを使用することをすでに決定している場合、実際のREST制約は次のとおりです。

    1. クライアントとサーバーの間の事前の(「帯域外」の)知識を前提としてはなりません。 *

その観点から、上記の提案#1を見てください。あなたは顧客があなたのシステムに含まれている都市の鍵を知っていると思いますか?間違っています-それは安らかではありません。したがって、サーバーは何らかの方法で都市のリストを選択肢のリストとして提供する必要があります。それで、あなたはここに世界のすべての都市をリストするつもりですか?そうではないと思いますが、これをどのように計画しているかについて、いくつかの作業を行う必要があります。これにより、次のことが可能になります。

    1. A REST APIは、リソースの表現とアプリケーションの状態の駆動に使用されるメディアタイプの定義に、ほとんどすべての記述的努力を費やす必要があります...

言及されたロイフィールディングのブログを読むことはあなたをかなり助けると思います。

13
nepdev

RESTfulでは、API URLの設計は非常に重要ではないはずです-または、発見可能性はURLパスではなくハイパーテキストにエンコードされるため、少なくとも副次的な問題です。ここStackOverflowの RESTタグwiki にリンクされているリソースをご覧ください。

ただし、UC用に人間が読めるURLを設計する場合は、次のことをお勧めします。

  1. 作成/更新/クエリするリソースタイプをURLの最初の部分として使用します(APIプレフィックスの後)。したがって、誰かがURLを見ると、このURLがどのリソースを指しているかがすぐにわかります。 GET /Api/Employees...は、APIから従業員リソースを受け取る唯一の方法です。

  2. リソースの関係に関係なく、リソースごとに一意のIDを使用します。そう GET /Api/<CollectionType>/UniqueKeyは有効なリソース表現を返す必要があります。従業員がどこにいるかを心配する必要はありません。 (ただし、返還された従業員は、所属するビジネス(および便宜上市)へのリンクを持っている必要があります。)GET /Api/Employees/Z6Wは、場所に関係なく、このIDを持つ従業員を返します。

  3. 特定のリソースを取得する場合:クエリパラメータを最後に配置します(質問で説明されている階層順序ではなく)。 URLクエリ文字列(GET /Api/Employees?City=X7N)または行列パラメータ式(GET /Api/Employees;City=X7N;Business=A4X,A5Y)。これにより、特定の都市のすべての従業員のコレクションを、所属するビジネスに関係なく簡単に表現できます。

サイドノード:

私の経験では、初期の階層ドメインデータモデルは、プロジェクト中に発生する追加の要件に耐えることはめったにありません。あなたの場合:2つの都市にあるビジネスを考えてみましょう。 2つの別々のビジネスとしてモデル化することで回避策を作成できますが、半分の時間を1つの場所で、残りの半分を別の場所で働く従業員はどうでしょうか。またはさらに悪いことに:彼がどのビジネスで働いているかは明らかですが、どの都市で、それは定義されていませんか?

3
xwoker

私が見る3番目の方法は、企業と従業員にリソースをルート化し、クエリパラメーターを使用してコレクションをフィルター処理することです。

GET Api/Businesses?city=ABC                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Employees?businesses=X7N                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

どちらのソリューションもRESTサブリソースの概念を使用しているため、サブリソースが親リソースに含まれている必要があります。

GET Api/City/ABC/Businesses

それに応じて、以下によって提供されるデータも返す必要があります。

  GET Api/City/ABC/Businesses/X7N                 
  GET Api/City/ABC/Businesses/X7N/Employees 

似ている:

GET Api/Businesses/X7N

これは、以下によって提供されるデータを返す必要があります。

GET Api/Businesses/X7N/Employees

応答のサイズが大きくなり、生成に必要な時間が長くなります。

REST APIをクリーンにするために、各リソースには、パターンの下で休む1つの境界付きURIのみが必要です。

 GET  /resources
 GET  /resources/{id}
 POST /resources
 PUT  /resources/{id}

リソース間のリンクを作成する必要がある場合は、 [〜#〜] hateoas [〜#〜] を使用します。

2
ragnor

例1に進みます。サーバーの観点からは、不要な情報について心配する必要はありません。 URLは、クライアントの観点から、独自の方法でリソースを明確に識別する必要があります。クライアントが実際に/Employee/12であることを最初に知らずに、/Businesses/X7N/Employees/12が何を意味するのかわからない場合、最初のURLは冗長であるように見えます。

クライアントは、URLを構成する個々のパラメーターではなく、URLを処理する必要があるため、長いURLでも問題はありません。クライアントにとって、それらは単なる文字列です。サーバーは、クライアントがURLを作成する必要がある個々のパラメーターではなく、必要な処理を実行するようにクライアントにURLを指示する必要があります。

1
Cormac Mulhall