Resources-Oriented Web Services (ROWS) is a set of technologies that enable the programmatic discovery, description and invocation of actions on resources.
It connects the human web with the programmable web. It is an alternative to Service-Oriented Architectures that is more aligned with how the web works (URLs and A Uniform Interface: REST. The "R" in URL is the key part.).
This is part of a series of posts. You want to read this and this before carrying on.
This is a human-readable walk through of a slightly more technical specification (build).
This is a human-readable walk through of a slightly more technical specification (build).
What problems are we set to solve again?
Our goal is to connect the "human web" to the "programmable web". Our challenge is to automate what we currently do manually as humans.
We, as a community, haven't yet converged on the following:
- A general framework, a way for each individual service to tell clients about its resource design, its representation formats and the links it provides between resources.
- A language with a vocabulary that can describe the variety of RESTful and hybrid services. A document written in this language could script a generic web service client, making it act like a custom-written wrapper. More specifically, we'll need to tell clients:
- What semantic operations are available to be performed
- Which HTTP method to use
- What the expected entity-body looks like
- What to expect to get back after you invoke
Lets look at what our starting point looks like
The human web starts by using a browser to send a GET request to a resource via a URL. Lets say you were looking for booking a cab, here is what you'd do under the hoods:
GET /mountain-view HTTP/1.1
Host: www.yellowcab.com
And the server responds:
HTTP/1.1 200 OK Content-Type: text/html
Which means that from this information a computer can send a request like the following with confidence:
+------------------------+
| Actions | <- missing gap #2
+------------------------+
| Things | <- missing gap #1
+------------------------+
| JSON-LD | <- hypermedia
| microdata |
+------------------------+
| REST | <- ROA vs SOA
+------------------------+
| HTTP | <- URIs, methods
+------------------------+
HTTP/1.1 200 OK Content-Type: text/html
<html> <body> <span>Welcome to Yellow Cab Mountain View!</span> <a href="/moutain-view/reservations"> Click here to book a cab! </a> </body> </html>
Now, that's great for humans to consume, but a computer can't tell the difference between this and a web page about frogs.
Enter JSON-LD, microdata and schema.org
The first step to help computers make sense of this page is to tell it explicitly what this is about.
There are a few good methods for transporting linked-data in HTML, but my favorite are JSON-LD and microdata*.
* I think that JSON-LD is a more scalable approach overall for large/complex instances, but microdata is easier to grasp on simpler examples. So I'm going to use microdata here in my examples, but bear in mind that I actually prefer JSON-LD a lot better in practice.
That alone isn't sufficient. You need a machine readable description of a taxi stand. Something that a computer could understand. This is where schema.org comes in: it provides a vocabulary that describes things in the universe in a manner that computers can digest.
This is what this web page would look like:
HTTP/1.1 200 OK
Content-Type: text/html
<html> <body itemscope itemid="/mountain-view" itemtype="http://schema.org/TaxiStand" > <span itemprop="description"> Welcome to Yellow Cab Mountain View! </span> <a itemprop="reservations" href="/mountain-view/reservations" itemscope itemtype="http://schema.org/ItemList"> Click here to book a cab! </a> </body> </html>
Now, this basically addresses problem #1 I raised above. It gives you a general framework (json-ld/microdata + schema.org) to describe things in a manner that computers can understand.
A computer now knows:
- This resource is a TaxiStand
- This TaxiStand has a description
- This TaxiStand has an ItemList of reservations
But it doesn't yet know what it can do with it.
The link to the programmable web
The programmable web exists in APIs, but it can't be easily found by computers. So, lets add a link between this specific taxi stand and where it can be found in the yellow cab APIs:
<body itemscope itemid="/mountain-view" itemtype="http://schema.org/TaxiStand" > <meta itemprop="alternate" itemscope itemtype="http://schema.org/ApiUrl" content="http://api.yellowcab.com/moutain-view"/>
Now we know that this Taxi Service is linked to a specific API.
If you GET-ed that URL you'd get something like the following:
GET /mountain-view HTTP/1.1
Host: api.yellowcab.com
Host: api.yellowcab.com
And the server responds:
HTTP/1.1 200 OK
Content-Type: application/json+ld
{ "@context": "http://schema.org", "@type": "TaxiStand", "@id": "/mountain-view", "description": "Welcome to Yellow Cab!", "reservations": { "@type": "ItemList", "@id": "/mountain-view/reservations", } }
That's much more like what computers can understand. There is a Content-Type header that tells computers how to parse it and inside the hypermedia there is machine-readable information about the resource.
But, can a computer tell what to *do* with these resources?
If you wanted to create a reservation, how far would HTTP take you?
The closest to an API discovery mechanism in HTTP is the OPTIONS request.
OPTIONS /mountain-view/reservations HTTP/1.1
Host: api.yellowcab.com
And it could respond:
Host: api.yellowcab.com
And it could respond:
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
POST requests can take you a long way, but as LSM pointed out earlier, it is not sufficient. How would you know:
- What entity-body the POST request takes?
- Whether POST is not an overloaded POST (e.g. RPC-Style POST)?
Introducing Actions
Actions gives you the vocabulary to describe what can be done with resources. It doesn't require you to make an extra request, but it is rather attached inline with the resources.
It consists of three core mechanisms:
- A mechanism to link Actions with Things
- A taxonomy of Actions with well defined semantics and invocation constrains
- A vocabulary to specify what your parameters looks like
Here is what the JSON-LD response could look like:
HTTP/1.1 200 OK
Content-Type: application/json+ld
{ "@context": "http://schema.org", "@type": "TaxiStand", "@id": "/mountain-view", "description": "Welcome to Yellow Cab!", "reservations": { "@type": "ItemList", "@id": "/mountain-view/reservations", "operation": { "@type": "CreateAction", "expects": { "@type": "SupportedClass", "subClassOf": "http://schema.org/TaxiReservation", } } } }
You could have also equally found that information in the HTML markup *:
<a itemprop="reservations" href="/mountain-view/reservations" itemscope itemtype="http://schema.org/ItemList"> <meta itemprop="alternate" itemscope itemtype="http://schema.org/ApiAppUrl" content="http://api.yellowcab.com/moutain-view/reservations" /> <div itemprop="operation" itemscope itemtype="http://schema.org/CreateAction"/> <meta itemprop="expects" itemscope itemtype="http://schema.org/SupportedClass" content="http://schema.org/TaxiReservation"/> </div> </div> Click here to book a cab! </a>
* I agree that this looks a bit verbose, but I'll show you later how to make this more concise. Hint: it has to do with linked data.
Now, that tells you *everything* a computer needs:
- This is a TaxiStand.
- There is an API entry point here
http://api.yellowcab.com/moutain-view/ - This TaxiStand has an ItemList of reservations.
- The reservation's ItemList takes CreateAction operations, which has *very* specific semantics (as well as a specification of what it means to invoke it, e.g. it is tight a POST request because it was defined that way).
- To create a reservation, you pass an instance of a TaxiReservation.
Which means that from this information a computer can send a request like the following with confidence:
POST /mountain-view/reservations HTTP/1.1
Host: api.yellowcab.com
Content-Type:application/json+d;charset=utf-8
Content-Length:207
And the server should respond something like the following:
HTTP/1.1 201 Created
Location:
http://api.yellowcab.com/moutain-view/reservations/32523325225
OPTIONS /mountain-view/reservations/32523325225 HTTP/1.1
Host: api.yellowcab.com
And it could respond:
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, PATCH
Accept-Patch: application/json+ld
GET /mountain-view/reservations/32523325225 HTTP/1.1
Host: api.yellowcab.com
And now the server can respond to you:
PATCH /mountain-view/reservations/32523325225 HTTP/1.1
Host: api.yellowcab.com
Content-Type:application/json+d;charset=utf-8
Content-Length:100
Accept: application/json+ld
Which now sets the state of the reservation to cancelled:
HTTP/1.1 200 OK
Host: api.yellowcab.com
Content-Type:application/json+d;charset=utf-8
Content-Length:207
{ "@context": "http://schema.org/", "@type": "TaxiReservation", "pickUpLocation": "1600 Amphitheatre Parkway, Mountain View, CA", "pickUpTime": "2pm", "numberOfPassengers": "1" }
And the server should respond something like the following:
HTTP/1.1 201 Created
http://api.yellowcab.com/moutain-view/reservations/32523325225
This is where hypermedia comes in again.
Hypermedia is an extremely powerful concept. It allows you to hop from one resource to another following links. That's quite powerful.
You just got a resource created, lets take a peak at what it can do:
Host: api.yellowcab.com
And it could respond:
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, PATCH
Accept-Patch: application/json+ld
And you'd be quite excited knowing that this is a mutable resource because it takes a PATCH HTTP method.
It tells you additionally that it takes a JSON-LD patch document, which is quite informative too.
But as much as with POST, you wouldn't know enough what is the application semantics of the PATCH operation (e.g. what does it mean to "patch"? is it to update the pick up time? the drop off location?).
Enter actions again. Lets GET this resource:
It tells you additionally that it takes a JSON-LD patch document, which is quite informative too.
But as much as with POST, you wouldn't know enough what is the application semantics of the PATCH operation (e.g. what does it mean to "patch"? is it to update the pick up time? the drop off location?).
Enter actions again. Lets GET this resource:
GET /mountain-view/reservations/32523325225 HTTP/1.1
Host: api.yellowcab.com
And now the server can respond to you:
HTTP/1.1 200 OK
Content-Type: application/json+ld
Accept: application/json+ld
Accept: application/json+ld
{ "@context": "http://schema.org", "@type": "TaxiReservation", "@id": "/mountain-view/reservations/32523325225", "reservationStatus": "CONFIRMED", "operation": { "@type": "CancelAction" } }
And because there is a very specific application semantic for CancelAction ("The act of asserting that a future event/action is no longer going to happen.") and a very specific definition of what it means to "cancel" a resource (in HTTP terms it is a PATCH request, according to the definition of "canceling"), it is well defined for a computer to send a PATCH request like the following:
PATCH /mountain-view/reservations/32523325225 HTTP/1.1
Host: api.yellowcab.com
Content-Type:application/json+d;charset=utf-8
Content-Length:100
Accept: application/json+ld
{ "@context": "http://schema.org/", "@type": "CancelAction" }
Which now sets the state of the reservation to cancelled:
HTTP/1.1 200 OK
Content-Type: application/json+ld
{ "@context": "http://schema.org", "@type": "TaxiReservation", "@id": "/mountain-view/reservations/32523325225", "reservationStatus": "CANCELLED", }
Note too that, once you cancelled, the "CancelAction" operation goes away, because that operation is no longer applicable to a CANCELLED reservation.
To Wrap Things Up
Phew, that was a lot of information. Here is where I think things fit:
+------------------------+
| Actions | <- missing gap #2
+------------------------+
| Things | <- missing gap #1
+------------------------+
| JSON-LD | <- hypermedia
| microdata |
+------------------------+
| REST | <- ROA vs SOA
+------------------------+
| HTTP | <- URIs, methods
+------------------------+
OK, that was an interesting read. But ...
... there is so much more to talk about.
The devil is on the details and we haven't gotten yet to how things like collections, authentication, different transport mechanisms (e.g. mobile applications, email messages) and gap #3 should look like.
Stay tuned. More to follow.