Reputation: 281
For simplicity, say I have a resource users. The HTTP call GET users/ returns a list of links to concrete users:
<users>
<link rel='user' href='/users/user/1/'/>
<link rel='user' href='/users/user/2/'/>
<link rel='user' href='/users/user/3/'/>
....
</users>
The result representation is described in a specific media type:
application/vnd.company.Users+xml
In our frontends, we want to display a table with all users. This means we need to be able to fetch user information to display, such as the name, gender, friends, ... I would like to avoid that we need a separate request for each user (GET /users/user/x/) to retrieve this information. In addition, some frontends will only display the name, while other frontends will display the name and his/her friends. And so on.
In essence, we are still returning users, but with extentions depending on what the frontend needs.
Which option would you choose? Why?
(1) Make GET users/ customizable via parameters such that the customizations are listed. Depending on the customizations , different media types might be returned, since the syntax of one version/combination might be very different than one of another version/combination:
GET users/ -> application/vnd.company.Users+xml
GET users/?fields=name,gender -> application/vnd.company.Users+xml
GET users/?fields=name,gender,friends -> application/vnd.company.UsersWithFriends+xml
(2) Different resources are created to distinguish different between media types. Parameters are still used for basic customizations covered by the media type. This gives:
GET users?fields=name -> application/vnd.company.Users+xml
GET users?fields=name,gender -> application/vnd.company.Users+xml
GET users_with_friends?fields=gender -> application/vnd.company.UsersWithFriends+xml
(3) The same as (1), but instead of parameters, the desired media type is set by the client in the Accept header. Customizable fields covered by the media type are still set via parameters:
GET users/?fields=name ACCEPT application/vnd.company.Users+xml
GET users/?fields=name,gender ACCEPT application/vnd.company.Users+xml
GET users/?fields=name,gender ACCEPT application/vnd.company.UsersWithFriends+xml
(4) Something else?
To answer my own question, I think that:
Do you agree? What do you think?
Upvotes: 1
Views: 291
Reputation: 142272
Personally, I'm not a fan of using query string parameters to allow clients to pick the data elements they wish to include in a representation. I find it makes it hard to optimize the server and it pollutes the cache with many overlapping variants. Also, you really shouldn't try and use conneg to select between representations that contain different sets of data. Conneg is really just for selecting the serialization format.
With the Hal media type you can approach this problem a bit differently. Consider a service with a root representation that looks like:
<resource rel="self"
href="http://example.org/userservice"
xmlns:us="http://example.org/userservice/rels">
<link rel="us:users" name="users" href="http://example.org/users">
<link rel="us:userswithfriends" href="http://example.org/userswithfriends">
</resource>
When you use hal, instead of using the media type documentation to describe your application domain, you can use link relations. In this case, the us:users
link points to a document that contains a list of users. I know the namespace stuff looks a bit wierd, but it is not really being used as an XML namespace, just as way of making a Compact URI (CURIE). When you invent your own rel values, they need to be specified in the form of a URI to try and ensure uniqueness.
The list of users would look something like:
<resource rel="self"
href="http://example.org/users"
xmlns:us="http://example.org/userservice/rels">
<resource rel="us:user" name="1" href="/user/1">
<name>Bob</name>
<age>45</age>
<resource>
<resource rel="us:user" name="2" href="/user/2">
<name>Fred</name>
<age>Bill</age>
<resource>
</resource>
and 'us:userswithfriends' points to a different resource that contains the list of users with each user containing a list of friends.
<resource rel="self"
href="http://example.org/users"
xmlns:us="http://example.org/userservice/rels">
<resource rel="us:user" name="1" href="/user/1">
<name>Bob</name>
<resource rel="us:friend" name="1" href="/user/10">
<name>Sheila</name>
<resource>
<resource rel="us:friend" name="2" href="/user/74">
<name>Robert</name>
<resource>
<resource>
<resource rel="user" name="2" href="/user/2">
<name>Fred</name>
<resource rel="us:friend" name="1" href="/user/14">
<name>Bill</name>
<resource>
<resource rel="us:friend" name="2" href="/user/33">
<name>Margaret</name>
<resource>
<resource>
</resource>
With hal it is the documentation of your rels (us:users, us:friend) that decribes what data elements are allowed to exist in the resource element. You are free to embed all of the data of the resource, or more likely just a subset of the data. If the client wants to access a completely representation of the embedded resource then it can follow the provided link.
Upvotes: 1
Reputation: 5615
(3) is surely the best using strict Media Type, but would require specific HTTP Request client and won't be accessible through basic URL open library or browser.
Why not using solution 1 with another extra parameter : names "expect" or "as". ie: users/?fields=name,gender&expect=application/vnd.company.Users+xml users/?fields=name,gender&expect=application/vnd.company.UsersWithFriends+xml
This would be the same as ACCEPT solution but won't need very custom client library to forge the request. However you'll have to parse the parameter to provide correct output (the (3) would also have this requirement for parsing the ACCEPT)
Upvotes: 1