composite key resource REST service
Asked Answered
P

2

26

I've come across a problem at work where I can't find information on the usual standard or practice for performing CRUD operations in a RESTful web service against a resource whose primary key is a composite of other resource ids. We are using MVC WebApi to create the controllers. For example, we have three tables:

  • Product: PK=ProductId
  • Part: PK=PartId
  • ProductPartAssoc: PK=(ProductId, PartId)

A product can have many parts and a part can be a component of many products. The association table also contains additional information relevant to the association itself than needs to be editable.

We have ProductsController and PartsController classes that handle the usual GET/PUT/POST/DELETE operations using route templates defined as: {controller}/{id}/{action} such that the following IRIs work:

  • GET,POST /api/Products - returns all products, creates a new product
  • GET,PUT,DELETE /api/Products/1 - retrieves/updates/deletes product 1
  • GET,POST /api/Parts - returns all parts, creates a new part
  • GET,PUT,DELETE /api/Parts/2 - retrieves/updates/deletes part 2
  • GET /api/Products/1/Parts - get all parts for product 1
  • GET /api/Parts/2/Products - get all products for which part 2 is a component

Where I am having trouble is in how to define the route template for ProductPartAssoc resources. What should the route template and IRI look like for getting the association data? Adhering to convention, I would expect something like:

  • GET,POST /api/ProductPartAssoc - returns all associations, creates an association
  • GET,PUT,DELETE /api/ProductPartAssoc/[1,2] - retrieves/updates/deletes association between product 1 and part 2

My coworkers find this aesthetically displeasing though and seem to think it would be better to not have a ProductPartAssocController class at all, but rather, add additional methods to the ProductsController to manage the association data:

  • GET,PUT,DELETE /api/Products/1/Parts/2 - get data for the association between product 1 and part 2 rather than data for part 2 as a member of part 1, which would conventionally be the case based on other examples such as /Book/5/Chapter/3 that I have seen elsewhere.
  • POST No clue here what they expect the IRI to look like. Unfortunately, they're the decision makers.

At the end of the day, I guess what I am seeking is either validation, or direction that I can point to and say "See, this is what other people do."

What is the typical practice for dealing with resources identified by composite keys?

Pili answered 23/4, 2013 at 22:18 Comment(3)
For linking entities, you could model your uri space like the OData protocol specifies for Odata services. i am not suggesting to implement an OData service, but its worth looking at it as it provides a useful insight and is closer to your problem. Look here for managing links between entities: odata.org/documentation/odata-v2-documentation/operations/…Incursive
What does ProductPartAssoc look like? And, what are the CRUD operations that it supports?Leakage
The example was arbitrary, but the ProductPartAssoc class might look something like (sorry for terseness - limited space here): class ProductPartAssoc { int ProductId; int PartId; int amountUsed; decimal assemblyCost; int installerId; } and would need to support all four CRUD ops - Create, Retrieve, Update, Delete.Pili
P
21

I too like the aesthetics of /api/Products/1/Parts/2. You could also have multiple routes go to the same action, so you could double up and also offer /api/Parts/2/Products/1 as an alternate URL for the same resource.

As for POST, you already know the composite key. So why not eliminate the need for POST and just use PUT for both creation and updates? POST to a collection resource URL is great if your system generates the primary key, but in cases where you have a composite of already known primary keys, why do you need POST?

That said, I also like the idea of having a separate ProductPartAssocController to contain the actions for these URL's. You would have to do a custom route mapping, but if you're using something like AttributeRouting.NET that is very easy to do.

For example we do this for managing users in roles:

PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1

6 URL's, but only 3 actions, all in the GrantsController (we call the gerund between users and roles a "Grant"). Class ends up looking something like this, using 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
}

This seems a fairly intuitive approach to me. Keeping the actions in a separate controller also makes for leaner controllers.

Pheidippides answered 24/4, 2013 at 17:35 Comment(1)
Thanks for the insight :-) Two things: (1) Thanks for the tip on AttributeRouting.Net - awesome stuff. (2) In retrospect, you are right about not needing a POST operation. Thanks for pointing that out. I marked your answer as answering my question...Pili
I
1

I suggest:

  • POST /api/PartsProductsAssoc: Create link between part and product. Include part and product ids in POST data.
  • GET, PUT, DELETE /api/PartsProductsAssoc/<assoc_id>: read/update/delete link with <assoc_id> (not part or product id, yes, this means creating a new column in your PartsProductsAssoc table).
  • GET /api/PartsProductsAssoc/Parts/<part_id>/Products: get list of products associated with the given part.
  • GET /api/PartsProductsAssoc/Products/<product_id>/Parts: get list of parts associated with the given product.

Reasons to take this approach:

  • Single, fully-qualified URI for each link.
  • Modifying a link modifies a single REST resource.

For more info, see https://www.youtube.com/watch?v=hdSrT4yjS1g at 56:30.

Inflationary answered 19/2, 2015 at 21:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.