この質問は、最適なREST API設計と、ネストされたリソースとルートレベルのコレクションのどちらかを選択するために直面している問題についてです。
概念を示すために、コレクションCity
、Business
、および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を作成しました。
どちらの解決策も私にはあまりきれいに見えません。
POST Api/Businesses/X7N7/Employees
などのビジネスルートの下に存在する必要があります。すべてがさらに混乱します。私が考えていない、よりクリーンで3番目の方法はありますか?
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
を維持します。したがって、あなたが言うように、Employees
はBusinesses
クラスルートからのみアクセスする必要があります。しかし、これはREST要件ではなく、他の代替手段も完全にRESTfulです。
これはHATEAOSの原則の適用と対になっていることに注意してください。 APIでは、都市にあるビジネスのリストは、API/Businesses
へのリンクのリストである可能性があります(おそらく理論的な観点からはそうあるべきです)。ただし、これは、クライアントがリスト内のアイテムごとにサーバーへのラウンドトリップを1回実行する必要があることを意味します。これは効率的ではなく、実用的であるために、私が行うことは、この例ではAPI/Businesses
となるURIへのself
リンクとともにビジネスの表現をリストに埋め込むことです。
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を見てください。あなたは顧客があなたのシステムに含まれている都市の鍵を知っていると思いますか?間違っています-それは安らかではありません。したがって、サーバーは何らかの方法で都市のリストを選択肢のリストとして提供する必要があります。それで、あなたはここに世界のすべての都市をリストするつもりですか?そうではないと思いますが、これをどのように計画しているかについて、いくつかの作業を行う必要があります。これにより、次のことが可能になります。
言及されたロイフィールディングのブログを読むことはあなたをかなり助けると思います。
RESTfulでは、API URLの設計は非常に重要ではないはずです-または、発見可能性はURLパスではなくハイパーテキストにエンコードされるため、少なくとも副次的な問題です。ここStackOverflowの RESTタグwiki にリンクされているリソースをご覧ください。
ただし、UC用に人間が読めるURLを設計する場合は、次のことをお勧めします。
作成/更新/クエリするリソースタイプをURLの最初の部分として使用します(APIプレフィックスの後)。したがって、誰かがURLを見ると、このURLがどのリソースを指しているかがすぐにわかります。 GET /Api/Employees...
は、APIから従業員リソースを受け取る唯一の方法です。
リソースの関係に関係なく、リソースごとに一意のIDを使用します。そう GET /Api/<CollectionType>/UniqueKey
は有効なリソース表現を返す必要があります。従業員がどこにいるかを心配する必要はありません。 (ただし、返還された従業員は、所属するビジネス(および便宜上市)へのリンクを持っている必要があります。)GET /Api/Employees/Z6W
は、場所に関係なく、このIDを持つ従業員を返します。
特定のリソースを取得する場合:クエリパラメータを最後に配置します(質問で説明されている階層順序ではなく)。 URLクエリ文字列(GET /Api/Employees?City=X7N
)または行列パラメータ式(GET /Api/Employees;City=X7N;Business=A4X,A5Y
)。これにより、特定の都市のすべての従業員のコレクションを、所属するビジネスに関係なく簡単に表現できます。
サイドノード:
私の経験では、初期の階層ドメインデータモデルは、プロジェクト中に発生する追加の要件に耐えることはめったにありません。あなたの場合:2つの都市にあるビジネスを考えてみましょう。 2つの別々のビジネスとしてモデル化することで回避策を作成できますが、半分の時間を1つの場所で、残りの半分を別の場所で働く従業員はどうでしょうか。またはさらに悪いことに:彼がどのビジネスで働いているかは明らかですが、どの都市で、それは定義されていませんか?
私が見る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 [〜#〜] を使用します。
例1に進みます。サーバーの観点からは、不要な情報について心配する必要はありません。 URLは、クライアントの観点から、独自の方法でリソースを明確に識別する必要があります。クライアントが実際に/Employee/12
であることを最初に知らずに、/Businesses/X7N/Employees/12
が何を意味するのかわからない場合、最初のURLは冗長であるように見えます。
クライアントは、URLを構成する個々のパラメーターではなく、URLを処理する必要があるため、長いURLでも問題はありません。クライアントにとって、それらは単なる文字列です。サーバーは、クライアントがURLを作成する必要がある個々のパラメーターではなく、必要な処理を実行するようにクライアントにURLを指示する必要があります。