web-dev-qa-db-ja.com

RESTful APIとネストされたリソース

作業中のプロジェクト用に(Laravelを使用して)RESTful APIを構築したら、ネストされたリソースへの私のアプローチの観点から、(多くの読書から)大多数であると思われるものをパスで定義することによって追跡しました。

https://myapi.com/clients/{clientId}/tasks

のではなく:

https://myapi.com/tasks?client={clientId}

これ以上の結果のフィルタリング、および並べ替えなどは、クエリパラメータを使用して行われました。

https://myapi.com/clients/{clientId}/tasks?orderby=title

最初はこれは良いアプローチのように思われましたが、コントローラーとルートの観点から維持することがより困難になりました。たとえば、上記の場合、ClientsController、TasksController、およびClientsTasksControllerを作成しました。

TasksController

public function store() {}
public function updated() {}
public function destroy() {}

TasksClientsController

public function index($client_id) {}

そしてroutes

Route::resource('tasks', 'TasksController', ['except' => [
    'edit', 'index'
]]);
Route::resource('clients.tasks', 'ClientsTasksController', ['only' => [
    'index'
]]);

このアプリケーションは、このようなネストされたリソースをこれ以上必要としませんでした。比較的簡単でした。しかし、私は今、もっと複雑な関係を持つ既存のアプリケーション用のAPIを作成しようとしています。これはリーグ管理アプリで、以下のEER図です。

enter image description here

私が心配しているのは、多くの異なるデータでリソースをクエリする必要があることです-例えば:

  1. 特定の部門、シーズン、ラウンドのチーム
  2. チームIDとシーズンIDの両方による統計、およびおそらくラウンドIDも
  3. シーズン、ラウンド、ディビジョンで一致

上記の組み合わせ。

したがって、最終的なリソースごとにコントローラーとルートを用意する方が理にかなっているように見えますが、パス変数ではなくクエリパラメーターを使用します。

例えば:

https://myapi.com/matches?team_id={teamId}&season_id={seasonId}&roundId={roundId}

のではなく:

https://myapi.com/seasons/{seasonId}/teams/{teamId}/matches

これにより作成されるさまざまな追加のコントローラー。

しかし、私はこれがほとんどの人が示唆しているように見えるという点で穀物に反しているかもしれないと感じています-例えば here を見てください。 2番目のURLも私にはすっきりしているようです。

ヘルプ/推奨事項は非常にいただければ幸いです。

3
C Ivemy

L3成熟度API(HATEOAS)[1]を設計している場合、クライアントはURLを構築または記憶しません。この場合の最良のアプローチは、両方のシナリオをサポートする複数のマッピングを用意することです。ナビゲーションによるフェッチと、URIテンプレートパラメータの母集団によるクエリです(RFC 6570 [2])。

提供されたリンクへのナビゲーションによってフェッチする場合、パス構造の選択は、エンティティ間の関係が集約または構成のいずれかに基づいている必要があります。たとえば、プレーヤーのライフサイクルはチームのライフサイクルの境界内にないため、プレーヤーはトップレベルのエンティティです。プレイヤーはチームを変えることができるので、次の一連のルートを持つことは自然です:

  • rel=players/playersのプレーヤーのリストを参照しています
  • プレーヤーのrel=selfplayers/{playerId}です
  • rel=teams/teamsのチームのリストを参照しています
  • チームのrel=selfteams/{teamId}です
  • rel=seasons/seasonsの季節のリストを参照しています
  • rel=selfシーズンは/seasons/{seasonId}

すべてのコレクションエンドポイントは、クエリパラメータを受け入れて、応答をページ分割およびフィルタリングできます。

集計の例

プレーヤーのすべてのチームを知ることができ、その逆も可能ですが、コレクションリンクには、以前のナビゲーションでこれらのパラメーター値がクライアントによって検出可能な場合にのみ、URIテンプレートパラメーターが含まれます。

たとえば、すべてのクライアントはリソースのリストを取得するAPIのルートから始まります。この時点で、彼はrel=teamsを検出しますが、クライアントが置換するプレーヤーIDの値をまだ認識していないため、リンクはプレーヤーでフィルターするクエリパラメーターを持つことができません。リンクは、現時点では次のようにしか見えません:/teams{?team_name}現時点ではリンクrel=playersと同じです。これには、プレーヤー名によるフィルターのみが含まれ、チームによるフィルターは含まれません。次のようになります:/players{?first_name}

ただし、/teamsに移動すると、rel=playersにリンク/teamsが見つかります。このリンクには、team_idパラメータが含まれています。これは、可能な値がすでにクライアントによって検出されているためです。次のようになります:/players{?team_id,first_name,last_name}

代わりに、プレーヤーとチームのrel=selfリクエストの応答で発見された次のルートを使用できます。

  • rel=player_teamsは、これまでにプレーヤーが行ったすべてのチームを一覧表示できます:/players/{playerId}/teams。上記で定義されているように、各チームにはrel=selfがあります。
  • チームリソース内のrel=team_playersは、/teams/{teamId}/playersを介してチームプレーヤーのリストを参照します。上記で定義したように、各プレーヤーにはrel=selfがあります。

構成例

シーズンごとに分割が定義されている場合、そのライフサイクルは同じであり、構成関係があります。シーズン内の試合も同様です。つまり、次のように一致するパスを作成するのが自然です。

/seasons/{seasonId}/matches/{matchId}

そして、以下のルートを介して部門内のマッチに移動します:

/seasons/{seasonId}/divisions/{divisionId}/matches

または検索:

/seasons/{seasonId}/matches?division_id={divisionId}

REST APIテクノロジーが複数のマッピングをサポートしている場合、最後の2つは同じコントローラーで処理できます。この例のすべてのサブパスも有効なリクエストであり、それぞれの情報を返します。さらに、この例は次のような追加の集約ルートで拡張:

  • /seasons/{seasonId}/divisions/{divisionId}/teams-部門内のチームのリスト。各チームリソースには、上記で定義されたrel=selfリンクがあります。
  • /seasons/{seasonId}/statistics/players-シーズンのプレーヤー統計のリスト

参考文献

  1. https://martinfowler.com/articles/richardsonMaturityModel.html
  2. https://tools.ietf.org/html/rfc657
2
Ivan Gammel