Please note that in order to keep the JSON concise,
I've omitted the JSON-LD @context and used standard naming.
I had a conversation with a coworker about using JSON-LD and Hydra to add hypermedia to an existing RESTful service. I will walk you through our conversion.
Start with an object graph
It is our habit as RESTful service designers to think of our resources first and then the verbs that operate on those resources. With hypermedia I find that it is easier to think of the entire object graph of a service.
So, let us start with the definition of a "Resource". A Resource is simply a collection of key value pairs. Links are properties that point to other resources (bags of key/value pairs).
So in his service, he has two resources, a TopFive resource and an
Asset Resource.

Starting with the TopFive resource we have a link to Asset resources
called contents

Next he needs a way to fetch assets using an list of asset ids. This
is a link from TopFive to an AssetCollection resource. Let us call
this link assetMultiGet

So now our service's entire object graph looks like the following

Now that we have defined what our object graph looks like, we can design our hypermedia service based on the requirements of the clients.
Lens? Representations?
I find a good analogy to use is that URLs are lens into our service's object graph. These URLs will only show us one particular representation of our entire object graph (The R of REST!).
Let us start with the root resource ("/") which is a TopFive
resource. The "/" will give us a view of our object graph starting
with a TopFive resource and spider out from there.
The simpliest representation of our object graph could look like:
{
"@id": "/"
"@type": ["TopFive"],
}
Next we'll link this TopFive resource to Asset resources using the contents link:
{
"@id": "/"
"@type": ["TopFive"],
"contents": [
"/assets/1",
"/assets/2",
"/assets/3",
"/assets/4",
]
}
This is the simpliest way to represent our object graph including the
contents link. We have the TopFive resource at the root and
contents links to the assets via their URLs.
There is an issue with this particular view of our object graph. In order for a client to get access to the properties of each asset the client will need to dereference each URL.
To make it easier for the client, we can include some of the assets' properties. This way, the client will no longer need to dereference the assets via HTTP.
{
"@id": "/",
"@type": ["TopFive"],
"contents": [
{"@id": "/assets/1", "id": 1, "name": "Asset 1", "thumbnail": "..."},
{"@id": "/assets/2", "id": 2, "name": "Asset 2", "thumbnail": "..."},
{"@id": "/assets/3", "id": 3, "name": "Asset 3", "thumbnail": "..."},
{"@id": "/assets/4", "id": 4, "name": "Asset 4", "thumbnail": "..."},
]
}
This is just like the decisions we make when writing HTML pages. In HTML, we have to decide how much of the linked page needs to be included with the linking page.
The next link we need to include is assetMultiGet but now we have a
problem. This resource does not have a static URL. It has query
parameters.
Fortunately we have the hydra:IriTemplate class for linking to
algorithmic resources
{
"@id": "/",
"@type": ["TopFive"],
"assetMultiGet": {
"@id": "/assetMultiGet",
"@type": ["IriTemplate", "AssetCollection"],
"template": "/assetMultiGet{?assetId}",
"mapping": [{
"variable": "assetId",
"property": "id",
"required": true
}]
},
"contents": [
{"@id": "/assets/1", "name": "Asset 1", "thumbnail": "..."},
{"@id": "/assets/2", "name": "Asset 1", "thumbnail": "..."},
{"@id": "/assets/3", "name": "Asset 1", "thumbnail": "..."},
{"@id": "/assets/4", "name": "Asset 1", "thumbnail": "..."},
]
}
Wait, isn't assetMultiGet supposed to link to an
AssetCollection? The resource doesn't look like an AssetCollection (i.e. no
member property).
That is because assetMultiGet is linking to an view of an
AssetCollection resource that has no AssetCollection properties.
Instead what assetMultiGet is linking to is a resource (at
"/assetMultiGet") which is both an IriTemplate and an
AssetCollection resource. This resource could have either
IriTemplate properties or AssetCollection properties. In this
case, it only has IriTemplate properties.
Clients can now use the assetMultiGet IriTemplate properties to
construct a URL to access an AssetCollection resource.
The client will choose which assets it has in it's memory to multiget.
Lets say it uses /assets/1 and /assets/2.
The mapping states that in order to fill in the required assetId
parameter, it will use the id property.
So if the client requests /assetMultiGet?assetId=1,2:
{
"@id": "/assetMultiGet?assetId=1,2",
"@type": ["AssetCollection"],
"member": [
{"@id": "/assets/1", "name": "Asset 1", "thumbnail": "..."},
{"@id": "/assets/2", "name": "Asset 1", "thumbnail": "..."}
],
"assetMultiGet": {
"@id": "/assetMultiGet",
"@type": ["IriTemplate", "AssetCollection"],
"template": "/assetMultiGet{?assetId}",
"mapping": [{
"variable": "assetId",
"property": "id",
"required": true
}]
},
"topFive": {"@id": "/"}
}
A IriTemplate resource is the Hydra analog to an HTML form with a method of "get".
For the sake of completeness, I've included links back to
assetMulitGet and topFive to enable clients to navigate back to
those resources. It is a good practice to have a complete
digraph so clients can
access any resource in your graph to another resource:

Operation
The final way that we can interact with a service is via
hydra:Operation resources. This allows service developers to
describe POST/PUT/DELETE operations on resources.
His service is read only but if a client was allowed to delete a
resource an Asset resource might look like this:
{
"@id": "/asset/1",
"name": "Asset 1",
"thumbnail": "...",
"operation": [
{"method": "DELETE"},
{
"method": "PUT",
"expects": {
"supportedProperty": [
{"property": "name", "required": false},
{"property": "thumbnail", "required": false}
]
}
}
],
"assetMultiGet": {
"@id": "/assetMultiGet",
"@type": ["IriTemplate", "AssetCollection"],
"template": "/assetMultiGet{?assetId}",
"mapping": [{
"variable": "assetId",
"property": "id",
"required": true
}]
},
"topFive": {"@id": "/"}
}
So now we know that we can make DELETE and PUT requests to the /asset/1 URL.
Summary.
So to summarize:
- Start with the entire object graph of your service
- URLs are lens into your entire object graph
- @id are direct links i.e. <a /> tags
- hydra:IriTemplate resources are like <form method="get" /> tags
- hydra:operation links define POST/PUT/DELETE operations on the resource
Interesting...
While we were talking, an interesting organizational fact came to
light. He is only writing the top-5 service. Another developer is
writing the asset-multiget service.
This really hammers in the brilliance of the design of Hypermedia. We
no longer need to contain the entire object graph in one service. The
top-5 service can run and develop independently of the
asset-multiget service and both can be independent of the asset
service.
The boundaries between the services can be chosen to meet the needs for these services. Links enable us to transfer clients between these services as if it was one monolithic system.
Comments !