kiva / backbone.siren

Converts Siren JSON representations into Backbone Models
MIT License
14 stars 3 forks source link

Improved object traversal (aka deep linking) #21

Closed gxxcastillo closed 10 years ago

gxxcastillo commented 11 years ago

Following up on the deep linking feature proposed in #16.

This feature his has been super useful and while I haven't documented it yet, this is what the API currently looks like:

Backbone.Siren.resolve('baseEntity#nestedEntityName#anotherNestedEntityName');

I'd like to build on this by (in sequence):

  1. addressing the invalid url issue mentioned by @apsoto
  2. provide ability to fetch more than one entity at a time.
  3. provide ability to traverse into a collection entity

Item 1 can be addressed by splitting the url into a "url" and a "selector" (Also, calling it "deep linking" is not accurate, its probably better described as "traversal")

Backbone.Siren.resolve('baseEntity', '#nestedEntityName#anotherNestedEntityName');

Or, alternatively:

Backbone.Siren.resolve({
     entity: 'baseEntity'
     , selector: '#nestedEntityName#anotherNestedEntityName'
});

A server might even be able to support something like this via a query parameter.

Item 2 can be addressed by simply accepting an array of the entity resource urls and selectors

Item 3 can be done using js notation:

Backbone.Siren.resolve('baseEntity', '#nestedCollectionEntity[0]anotherNestedEntityName');
apsoto commented 11 years ago

WRT item 1 (invalid URL):

I've been thinking about similar ideas for my api. Here's my thoughts:

http://api.example.com/purchase/1

Returns a purchase order resource which contains an entity link with rel ''customer".

You could return the purchase resource with a full customer entity like such:

http://api.example.com/purchase/1?expand=customer

If the purchase order resource contained multiple entity links with rel of 'orderItem' you could return all orderItems expanded

http://api.example.com/purchase/1?expand=orderItem

or a single one

http://api.example.com/purchase/1?expand=orderItem[0]

You can get more advanced like the examples you've already listed (nested entities, sibling entities).

Once you've implemented and used in production and see how it works in practice, you should write up a pull request to kevinswiber/siren so there can be some standardization on how a siren based API communicates support for such a feature.

Looking forward to seeing how it turns out.

gxxcastillo commented 10 years ago

Just a comment for now...still needs follow up...

Something to keep in mind here is that some requests are sent to the server for parsing and other requests we want parsed on the client.

For example, lets say you make the following request:

http://api.example.com/purchase/1?expand=orderItem

The server then knows to expand the entity that matches "orderItem"

However, there are some use cases where you might want to have the client handle this, and so you would do:

http://api.example.com/purchase/1#expand=orderItem

In this case, the client is requesting http://api.example.com/purchase/1 from the server, and then once that is received, making another request to expand orderItem

Mainly jotting this down as a note for myself when I come back to this at some point...

gxxcastillo commented 10 years ago

Some more jotting down, will have to follow up on the Siren mailing list...

In order to be able to iterate recursively down the entity chain, I had been thinking we could do (I'm using the > here to show nesting, however, I think its an illegal url character, I'm using it mainly to illustrate the concept for now**):

api.io/expand?one>two>three>four

However, this could be confusing as it's not in nama/value format and would be difficult to identify if there were more properties in the query string.

So instead we could do:

api.io/entity?expand=one>two>three>four
//or for client side parsing (for the rest of these examples the `?` and `#` can be used interchangeably): 
api.io/entity#expand=one>two>three>four

If you want to expand more than one sub-entity:

api.io/entity?expand[]=one&expand[]=another&expand[]=yet-another

If you want to expand an entity within a collection:

api.io/entity?expand[]=[2]&expand[]=[3] // expands items 2 and 3 in the collection

You could even combine the two:

api.io/entity?expand[]=one>two>three&expand[]=another&expand[]=more

There could even be an expandAll:

api.io/entity?expandAll=true

While not as "pretty" as I had hoped by having the references look more like html anchors with multiple hashes (eg, #one#two#three), this way is more robust and allows client side format to mirror that of the server side format. (because the links are now valid urls)

**All my use-cases thus far have been using the sub-entities "name". However, there are times where it might be useful to use the "rel" and/or "class" of the sub-entity. If so, it would probably make sense to standardize on 3 different prefixes. Imagine something like @name, !rel and .class. Note that as this can eventually get complicated and need some spaces, url encoding might be required

gxxcastillo commented 10 years ago

ok, one final thought here.

In trying to keep parity between client and server when parsing these urls, we need to address the end result of these requests.

When you request api.io/entity#expand=one>two>three>four, is your request resolved with 1) the root entity or 2) one of the sub-entities

I'm thinking the result would resolve with the root entity, and having it contain all the sub-entities according to the "expand" rules.

If we do this, we need another query parameter name for those times that you want the request resolved to the sub-entity (I've actually found this to be the more common use-case). For this, we can use "select" instead of "expand", and it MUST NOT be an array:

api.io/entity#select=one>two>three>four

Note that if "select" is used, a server which implements this will need to ignore any "expand" that is set in the same request.