How to Take Your API From RPC to Hypermedia in 7 Steps

Continued from page 3. 

In a REST API, state is transferred from the service to clients. Relations between resources are denoted by links in the representation of resources that can be traversed using the HTTP protocol. For example we could follow the http://todosapp.com/api/vocab#user property contained in the previous representation:

curl -X GET -H "Accept: application/ld+json" http://todosapp.com/api/version_3/users/1

To retrieve the JSON-LD representation:

{
  "@context": { "@vocab": "http://todosapp.com/api/vocab#" },
  "@id": "http://todosapp.com/api/users/1",
  "name": "John Doo",
  "email": "john.doo@test.com",
  "todos": { "@id": "http://todosapp.com/api/users/1/todos" }
}

This JSON-LD document can also be interpreted as a data graph:

<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#name> "John Doo" .
<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#email> "john.doo@test.com" .
<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#todos> <http://todosapp.com/api/users/1/todos> .

Both graphs can be merged in the client just appending the assertions:

<http://todosapp.com/api/users/1/todos/1> <http://todosapp.com/api/vocab#title> "Test" .
<http://todosapp.com/api/users/1/todos/1> <http://todosapp.com/api/vocab#description> "Test TODO" .
<http://todosapp.com/api/users/1/todos/1> <http://todosapp.com/api/vocab#user> <http://todosapp.com/api/users/1> .
<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#name> "John Doo" .
<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#email> "john.doo@test.com" .
<http://todosapp.com/api/users/1> <http://todosapp.com/api/vocab#todos> <http://todosapp.com/api/users/1/todos> .

To obtained the combined data graph:

We have been able to re-construct in the client a graph of data distributed across multiple HTTP resources just merging the underlying JSON-LD data model.

This distributed data graph model is specified in the RDF W3C recommendation and it opens multiple possibilities for clients.

For example, we could query the local data graph using the SPARQL and client library like RDFStore-JS.

PREFIX todos: <http://todosapp.com/api/vocab#>
SELECT ?name (COUNT(?todo) AS ?todos)
WHERE {
  ?user todos:name ?name .
  ?todo todos:user ?user
}
GROUP BY ?name

To obtain the following results:

|   ?name   |  ?todos  |
|-----------|----------|
| John Doo  | 1        |
|-----------|----------|

This ability to merge and query distributed information exposed as HTTP resources in a standard data format, is the foundation to build generic API clients completely decoupled from the server. It can be the core of a hypothetical generic client for APIs, equivalent to a Web browser for the Web of documents.

API Version 3.1: URIs

In version 3 of the API we have seen how to embed hyperlinks in the representations of resources. In this revision we are going to address a problem related to the way we are building identifiers for these resources.

The notion of a URI has evolved since the original conception of the Web.

At the beginning, in a Web of documents, URIs were just identifiers for these documents (or more technically, information resources), specifying their location in the network and the network protocol that should be used to retrieve them.

But URIs cannot only be used to locate documents, they can be used to identify resources in the REST sense. REST resources are concepts (technically non information resources) that cannot be transmitted using the HTTP protocol. Resources can mean anything, from a person to Mount Everest, the only condition is that resources need to be identified by URIs.

So the question is: what should a server do when a client issues a HTTP GET request for a URI identifying a non information resource like Mount Everest?

This problem is known as ‘HTTP Range 14’ after the issue were it was first raised.

From a REST point of view the solution is clear, the resource cannot be transmitted, but a representation for the resource agreed between client and server through content negotiation can be transmitted. The question is if URIs for the resource and the representation should be the same.

Different solutions have been proposed, one is to use HTTP re-directions to send clients from the resource to the representation of the resource.

Our favored solution is to use two different kind of URIs:

The interesting thing about this solution is that it offers a mechanism to automatically relate both URIs.

Hash URIs cannot be directly dereferenced by a HTTP client. If a client tries to dereference a hash URI the fragment is removed and the remaining URI, a potential URL, is sent in the request.

We can use this fact to distinguish the resource http://todosapp.com/api/users/1/todos/1#self from the JSON-LD document where the resource is described http://todosapp.com/api/users/1/todos/1. Moreover, the hash URI will appear inside the data graph encoded in the document.

We will change our API so every resource will be identified using a hash URI. An example representation for a resource will look like:

[
  {
    "@context": { "@vocab": "http://todosapp.com/api/vocab#" },
    "@id": "http://todosapp.com/api/users/1/todos/1#self",
    "title": "Test",
    "description": "Test TODO",
    "user": { "@id": "http://todosapp.com/api/users/1#self" }
  }
]

An example of an API addressing the HTTP Range 14 issue is dbpedia. If we try to request the Mount Everest resource (http://dbpedia.org/resource/Mount_Everest) in dbpedia with a JSON-LD representation (or with a web browser) we will be redirected to the right URL for the resource representation:

$ curl -iv -H "Accept: application/ld+json" http://dbpedia.org/resource/Mount_Everest

* Trying 194.109.129.58...
* Connected to dbpedia.org (194.109.129.58) port 80 (#0)
> GET /resource/Mount_Everest HTTP/1.1
> Host: dbpedia.org
> User-Agent: curl/7.43.0
> Accept: application/ld+json
>
< HTTP/1.1 303 See Other
HTTP/1.1 303 See Other
< Location: http://dbpedia.org/data/Mount_Everest.jsonld
Location: http://dbpedia.org/data/Mount_Everest.jsonld
# ...
<
* Connection #0 to host dbpedia.org left intact

$ curl -iv -H "Accept: text/html" http://dbpedia.org/resource/Mount_Everest

* Trying 194.109.129.58...
* Connected to dbpedia.org (194.109.129.58) port 80 (#0)
> GET /resource/Mount_Everest HTTP/1.1
> Host: dbpedia.org
> User-Agent: curl/7.43.0
> Accept: text/html
>
< HTTP/1.1 303 See Other
HTTP/1.1 303 See Other
< Location: http://dbpedia.org/page/Mount_Everest
Location: http://dbpedia.org/page/Mount_Everest
# ...
<
* Connection #0 to host dbpedia.org left intact

API Version 3.2: Collections

In this revision of the API we are going to introduce the concept of collection resources.

Up to this we have paid attention to individual resources, Users and Todos. We have also introduced hash URIs to identify them property and distinguish them from their representations. But if we look at URIs like http://todosapp.com/api/version_3_1/users they are being treated as URLs pointing to a JSON-LD document containing a data graph that can be fully partitioned in as many sub-graphs as user resources have been created in the service. The problem is that these collections of resources are not resources themselves. They don’t have a hash URI identifying them, they will never appear as subject resource in the client data graph.

Furthermore, since they are not resources, we cannot describe them. We cannot, for instance,  assert that the collection of users is paginated or at what time the last user was created.

Antonio Garrote Principal Engineer at Mulesoft in the mornings, researching on RESTful semantic web services in the evenings. Clojure enthusiast all day long.
 

Comments

Comments(1)

alexsmith84

Great article! I'm currently only learning but i do have some ideas for future projects and i feel like your tips will help me a lot! I'm currently trying to do something with doahomework.com API