主キーが他のリソースIDの複合であるリソースに対して、RESTful WebサービスでCRUD操作を実行するための通常の標準またはプラクティスに関する情報が見つからないという、職場での問題に遭遇しました。 MVCWebApiを使用してコントローラーを作成しています。たとえば、次の3つのテーブルがあります。
Product
:PK = ProductIdPart
:PK = PartIdProductPartAssoc
:PK =(ProductId、PartId)製品は多くの部品を持つことができ、部品は多くの製品のコンポーネントになることができます。アソシエーションテーブルには、編集可能である必要がある以外に、アソシエーション自体に関連する追加情報も含まれています。
次のIRIが機能するように、{controller}/{id}/{action}
として定義されたルートテンプレートを使用して、通常のGET/PUT/POST/DELETE操作を処理するProductsController
クラスとPartsController
クラスがあります。
/api/Products
-すべての商品を返し、新しい商品を作成します/api/Products/1
-製品1を取得/更新/削除します/api/Parts
-すべてのパーツを返し、新しいパーツを作成します/api/Parts/2
-パート2を取得/更新/削除します/api/Products/1/Parts
-製品1のすべての部品を取得します/api/Parts/2/Products
-パート2がコンポーネントであるすべての製品を取得します私が問題を抱えているのは、ProductPartAssocリソースのルートテンプレートを定義する方法です。アソシエーションデータを取得するためのルートテンプレートとIRIはどのようになりますか?慣例に従い、私は次のようなことを期待します。
/api/ProductPartAssoc
-すべての関連付けを返し、関連付けを作成します/api/ProductPartAssoc/[1,2]
-製品1とパート2の間の関連付けを取得/更新/削除します私の同僚はこれが美的に不快だと感じており、ProductPartAssocController
クラスをまったく持たない方が良いと考えているようですが、むしろProductsController
にメソッドを追加して関連付けデータを管理します。
/api/Products/1/Parts/2
-パート1のメンバーとしてパート2のデータではなく、製品1とパート2の間の関連付けのデータを取得します。これは、他の場所で見た/Book/5/Chapter/3
などの他の例に基づく従来のケースです。結局のところ、私が求めているのは、検証か、「ほら、これは他の人がしていることだ」と指摘できる方向性のどちらかだと思います。
複合キーによって識別されるリソースを処理するための一般的な方法は何ですか?
私も/api/Products/1/Parts/2
の美学が好きです。複数のルートを同じアクションに移動させることもできるので、2倍にして、同じリソースの代替URLとして/api/Parts/2/Products/1
を提供することもできます。
POSTに関しては、すでに複合キーを知っています。では、POSTの必要性を排除し、作成と更新の両方にPUTを使用してみませんか?システムが主キーを生成する場合、コレクションリソースURLへのPOSTは優れていますが、既知の主キーの複合がある場合、なぜPOSTが必要なのですか?
そうは言っても、これらのURLのアクションを含めるために別のProductPartAssocController
を用意するというアイデアも気に入っています。カスタムルートマッピングを実行する必要がありますが、 AttributeRouting.NET のようなものを使用している場合は、非常に簡単に実行できます。
たとえば、ロール内のユーザーを管理するためにこれを行います。
PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1
6つのURL、ただし3つのアクションのみ。すべてGrantsController
にあります(ユーザーとロールの間の動名詞を「許可」と呼びます)。 AttributeRouting.NET :を使用すると、クラスは次のようになります。
[RoutePrefix("api")]
[Authorize(Roles = RoleName.RoleGrantors)]
public class GrantsController : ApiController
{
[PUT("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[PUT("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage PutInRole(int userId, int roleId)
{
...
}
[DELETE("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[DELETE("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage DeleteFromRole(int userId, int roleId)
{
...
}
...etc
}
これは私にはかなり直感的なアプローチのようです。アクションを別のコントローラーに保持すると、よりスリムなコントローラーにもなります。
私は提案します:
/api/PartsProductsAssoc
:部品と製品の間にリンクを作成します。 POSTデータに部品IDと製品IDを含めます。/api/PartsProductsAssoc/<assoc_id>
:<assoc_id>
のリンクを読み取り/更新/削除します(パーツIDまたは製品IDではありません。はい、これは、PartsProductsAssocテーブルに新しい列を作成することを意味します)。/api/PartsProductsAssoc/Parts/<part_id>/Products
:指定された部品に関連する製品のリストを取得します。/api/PartsProductsAssoc/Products/<product_id>/Parts
:指定された製品に関連する部品のリストを取得します。このアプローチを採用する理由:
詳細については、 https://www.youtube.com/watch?v=hdSrT4yjS1g at56:30を参照してください。