Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to follow multiple relations from a resource? #7

Closed
jarib opened this issue May 25, 2014 · 5 comments
Closed

How to follow multiple relations from a resource? #7

jarib opened this issue May 25, 2014 · 5 comments

Comments

@jarib
Copy link

jarib commented May 25, 2014

Hi, and thanks for your work on this project! I'm a bit new to both node and hypermedia, so bear with me.

I'm trying out traverson for a HAL API I'm building, but can't figure out a good way of following multiple relations from a resource. Using the orders example, I may want to fetch both the ea:basket and ea:customer relations of the order I've just found.

These are the only ways to do it that I've found (code untested):

a. start from the root resource for every relation

api.newRequest().follow('ea:orders', 'ea:find')
   .withTemplateParameters({ id: 13 })
   .getResource(function(err, order) { ... });

api.newRequest().follow('ea:orders', 'ea:find', 'ea:basket')
   .withTemplateParameters({ id: 13 })
   .getResource(function(err, basket) { ... });

api.newRequest().follow('ea:orders', 'ea:find', 'ea:customer')
   .withTemplateParameters({ id: 13 })
   .getResource(function(err, customer) { ... });

b. create a new traverson instance based on the order response

api.newRequest().follow('ea:orders', 'ea:find')
   .withTemplateParameters({ id: 13 })
   .getResource(function(err, order) { 
     var orderApi = traverson.jsonHal.from(order._links.self.href);
     orderApi.newRequest().follow('ea:basket').getResource(function (err, basket) { ... });
     orderApi.newRequest().follow('ea:customer').getResource(function (err, customer) { ... });
   });

Here's a few ideas on how these could be made simpler:

c. re-use request with additional .follow calls (actual requests equivalent to ex. a)

var order = api.newRequest()
            .follow('ea:orders', 'ea:find')
            .withTemplateParameters({ id: 13 })


order.getResource(...)
order.follow('ea:basket').getResource(...)
order.follow('ea:customer').getResource(...)

d. have the resource object double as a client (actual requests equivalent to ex. b)

api.newRequest()
  .follow('ea:orders', 'ea:find')
  .withTemplateParameters({ id: 13 })
  .getResource(function(err, order) { 
     order.follow('ea:basket').getResource(...);
     order.follow('ea:customer').getResource(...)
  });

All of these feel suboptimal in different ways, both when it comes to performance (number of requests) and readability. It also seems like I'd have to do quite a bit of trickery to get a composed response object (consisting of order, basket and customer) that could be passed to a view or presenter. Though that may also just be my lack of node experience.

Going off into total fantasy for a moment, what I'm really after is a way of specifying all the relations I want up front, and simply get a composed response back. It'd have to be a quiet generic specification to support all the cases, but e.g. something like this:

var requestSpec = [
  { 
    rel: "ea:orders",
    follow: [
      {
        rel: "ea:find",
        params: {id: 13},
        follow: [{rel: "ea:basket"}, {rel: "ea:customer"}]
      }
    ]
  }
]

api.request(requestSpec).get(function(err, resp) {
  resp['ea:orders']                              // result of /orders
  resp['ea:orders']['ea:find']                    // result of /orders/13 wrapped, wrapped in array (since you may want to follow ea:find multiple times)
  resp['ea:orders']['ea:find'][0]['ea:basket']    // result of /baskets/321 wrapped in array
  resp['ea:orders']['ea:find'][0]['ea:customer']  // result of /customers/42 wrapped in array
});

Or possibly sugar it a bit

var order = api.follow("ea:orders").follow("ea:find", {id: 13})

order.follow('ea:basket')
order.follow('ea:customer')

order.getResources(...);

Perhaps something like this could be built on top of traverson. Any thoughts?

@basti1302
Copy link
Member

Thanks for your input. I think this are very interesting ideas and they also match the general "philosophy" of Traverson as a tool to make following links in Hypermedia APIs as easy as possible.

Of course, a feature like this would require a major part of Traverson to be rewritten. I'll probably won't get around to do this anytime soon. I would like to keep this open though and I might come back to this later.

Also, if someone else would like to take a stab at this I wouldn't mind.

We can also discuss API ideas here. I really like the power that the requestSpec thing would give users (the snippet you prefixed with "Going off into total fantasy for a moment"). The downside to this would be that it's much harder to use and understand than the current API.

@jarib
Copy link
Author

jarib commented Jun 10, 2014

Thanks for the reply!

I did a quick and dirty implementation of the proposed client for an API-backed app I'm working on over at holderdeord/hdo-front (see e.g api.js where it's used).

I've made it so that the "composed" response is basically a big HAL object where the requested relations are always present in _embedded. If a relation is already there, it won't fetch the linked resource at all. So there's a built-in assumption that whatever is in _embedded will match what you'd get from fetching the link, which I think the HAL spec doesn't actually require (i.e. embedded resources could be partial versions). I've also built in some pagination stuff that's not really standard.

This works OK, but it also feels like I'm specifying the rel structure twice - once for the requestSpec and then again when I'm pulling out the data. Perhaps it could be even terser where the request/response underneath is completely hidden. However you're right about it being quite harder to understand.

@jinder
Copy link

jinder commented Mar 30, 2015

+1 for this. It'd be nice to have a continuation option from the promise itself. For example:

   .follow("datasources").getResource().then((result, traverson) =>  {
      return traverson.follow("test").getResource();
    }).then(......);

@basti1302
Copy link
Member

I finally started to work on continuing traversals in this branch (better late than never, right? ;-) ). I'd love some feedback on the API (which is work in progress right now), so chime in to the discussion at issue #40 if you like.

@basti1302
Copy link
Member

Continuing link traversals has landed in 2.0.0, which should also enable the use case described in the original ticket.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants