作業中のプロジェクト用に(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図です。
私が心配しているのは、多くの異なるデータでリソースをクエリする必要があることです-例えば:
上記の組み合わせ。
したがって、最終的なリソースごとにコントローラーとルートを用意する方が理にかなっているように見えますが、パス変数ではなくクエリパラメーターを使用します。
例えば:
https://myapi.com/matches?team_id={teamId}&season_id={seasonId}&roundId={roundId}
のではなく:
https://myapi.com/seasons/{seasonId}/teams/{teamId}/matches
これにより作成されるさまざまな追加のコントローラー。
しかし、私はこれがほとんどの人が示唆しているように見えるという点で穀物に反しているかもしれないと感じています-例えば here を見てください。 2番目のURLも私にはすっきりしているようです。
ヘルプ/推奨事項は非常にいただければ幸いです。
L3成熟度API(HATEOAS)[1]を設計している場合、クライアントはURLを構築または記憶しません。この場合の最良のアプローチは、両方のシナリオをサポートする複数のマッピングを用意することです。ナビゲーションによるフェッチと、URIテンプレートパラメータの母集団によるクエリです(RFC 6570 [2])。
提供されたリンクへのナビゲーションによってフェッチする場合、パス構造の選択は、エンティティ間の関係が集約または構成のいずれかに基づいている必要があります。たとえば、プレーヤーのライフサイクルはチームのライフサイクルの境界内にないため、プレーヤーはトップレベルのエンティティです。プレイヤーはチームを変えることができるので、次の一連のルートを持つことは自然です:
rel=players
は/players
のプレーヤーのリストを参照していますrel=self
はplayers/{playerId}
ですrel=teams
は/teams
のチームのリストを参照していますrel=self
はteams/{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
-シーズンのプレーヤー統計のリスト