简体   繁体   中英

How to construct intersection in REST Hypermedia API?

This question is language independent. Let's not worry about frameworks or implementation, let's just say everything can be implemented and let's look at REST API in an abstract way. In other words: I'm building a framework right now and I didn't see any solution to this problem anywhere.

Question

How one can construct REST URL endpoint for intersection of two independent REST paths which return collections? Short example: How to intersect /users/1/comments and /companies/6/comments ?

Constraint

All endpoints should return single data model entity or collection of entities.

Imho this is a very reasonable constraint and all examples of Hypermedia APIs look like this, even in draft-kelly-json-hal-07 .

If you think this is an invalid constraint or you know a better way please let me know.

Example

So let's say we have an application which has three data types: products , categories and companies . Each company can add some products to their profile page. While adding the product they must attach a category to the product. For example we can access this kind of data like this:

  • GET /categories will return collection of all categories
  • GET /categories/9 will return category of id 9
  • GET /categories/9/products will return all products inside category of id 9
  • GET /companies/7/products will return all products added to profile page of company of id 7

I've omitted _links hypermedia part on purpose because it is straightforward, for example / gives _links to /categories and /companies etc. We just need to remember that by using hypermedia we are traversing relations graph.

How to write URL that will return: all products that are from company(7) and are of category(9)? In otherwords how to intersect /categories/9/products and /companies/7/products ?

Assuming that all endpoints should represent data model resource or collection of them I believe this is a fundamental problem of REST Hypermedia API, because in traversing hypermedia api we are traversing relational graph going down one path so it is impossible to describe such intersection because it is a cross-section of two independent graph paths.

In other words I think we cannot represent two independent paths with only one path. Normally we traverse one path like A->B->C , but if we have X->Y and Z->Y and we want all Y s that come from X and Z then we have a problem.

So far my proposition is to use query strings: /categories/9/products?intersect=/companies/9 but can we do better?

Why do I want this?

Because I'm building a framework which will auto-generate REST Hypermedia API based on SQL database relations. You could think of it as a trans compiler of URLs to SELECT ... JOIN ... WHERE queries, but the client of the API only sees Hypermedia and the client would like to have a nice way of doing intersections, like in the example.

I don't think you should always look at REST as database representation, this case looks more of a kind of specific functionality to me. I think I'd go with something like this:

/intersection/comments?company=9&product=5

I've been digging after I wrote it and this is what I've found ( http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api ):

Sometimes you really have no way to map the action to a sensible RESTful structure. For example, a multi-resource search doesn't really make sense to be applied to a specific resource's endpoint. In this case, /search would make the most sense even though it isn't a resource. This is OK - just do what's right from the perspective of the API consumer and make sure it's documented clearly to avoid confusion.

What You want to do is to filter products in one of the categories ... so following Your example if we have:

GET /categories/9/products

Above will return all products in category 9, so to filter out products for company 7 I would use something like this

GET /categories/9/products?company=7

You should treat URI as link to fetch all data (just like simple select query in SQL) and query parameters as where, limit, desc etc. Using this approach You can build complex and readable queries fe.

GET /categories/9/products?company=7&order=name,asc&offset=10&limit=20

All endpoints should return single data model entity or collection of entities.

This is NOT a REST constraint. If you want to read about REST constraints, then read the Fielding dissertation .

Because I'm building a framework which will auto-generate REST Hypermedia API based on SQL database relations.

This is a wrong approach and has nothing to do with REST.

By REST you describe possible resource state transitions (or operation call templates) by sending hyperlinks in the response. These hyperlinks consist of a HTTP methods and URIs (and other data which is not relevant now) if you build the uniform interface using the HTTP and URI standards, and we usually do so. The URIs are not (necessarily) database entity and collection identifiers and if you apply such a constraint you will end up with a CRUD API, not with a REST API.

If you cannot describe an operation with the combination of HTTP methods and already existing resources, then you need a new resource.

In your case you want to aggregate the GET /users/1/comments and GET /companies/6/comments responses, so you need to define a link with GET and a third resource:

GET /comments/?users=1&companies=6
GET /intersection/users:1/companies:6/comments
GET /intersection/users/1/companies/6/comments

etc...

RESTful architecture is about returning resources that contain hypermedia controls that offer state transitions. What i see here is a multistep process of state transitions. Let's assume you have a root resource and somehow navigate over to /categories/9/products using the available hypermedia controls. I'd bet the results would look something like this in hal:

{
  _links : {
     self : { href : "/categories/9/products"}
  },
  _embedded : {
     item : [
        {json of prod 1},
        {json of prod 2}
     ]
  }
}

If you want your client to be able to intersect this with another collection you need to provide to them the mechanism to perform this. You have to give them a hypermedia control. HAL only has links, templated links, and embedded as control types. let's go with links..change the response to:

{
  _links : {
     self : { href : "/categories/9/products"},
     x:intersect-with : [
          { 
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 1",
            title : "Company 6 products"
          },
          {
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 2",
            title : "Company 5 products"
          },
          {
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 3",
            title : "Company 7 products"
          }
     ]
  },
  _embedded : {
     item : [
        {json of prod 1},
        {json of prod 2}
     ]
  }
}

Now the client just picks the right hypermedia control (aka link) based on the title field of the link.

That's the simplest solution. But you'll probably say there's 1000's of companies i don't want 1000's of links...well ok if that;s REALLY the case...you just offer a state transition in the middle of the two we have:

{
  _links : {
     self : { href : "/categories/9/products"},
     x:intersect-options : { href : "URL to a Paged collection of all intersect options"}, 
     x:intersect-with : [
          { 
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 1",
            title : "Company 6 products"
          },
          {
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 2",
            title : "Company 5 products"
          },
          {
            href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 3",
            title : "Company 7 products"
          }
     ]
  },
  _embedded : {
     item : [
        {json of prod 1},
        {json of prod 2}
     ]
  }
}

See what i did there? an extra control for an extra state transition. JUST LIKE YOU WOULD DO IF YOU HAD A WEBPAGE. You'd probably put it in a pop up, well that's what the client of your app can do too with the result of that control.

It's really that simple...just think how you'd do it in HTML and do the same.

The big benefit here is that the client NEVER EVER needed to know a company or category id or ever plug that in to some template. The id's are implementation details, the client never knows they exist, they just executed Hypermedia controls..and that is RESTful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM