Show Me the Code
March 2, 2005
In my inaugural article, I outlined the four basic steps you needed to follow when creating a RESTful web service. Now let's take those basic steps and follow them through a worked example. To stay on familiar ground we'll create something that you may find familiar: a web bookmark service. It's hip, all the cool kids have one, and by the end of this article, so will we — or at least we'll have the specification for one. The actual implementation will be fleshed out over future articles.
The idea of a bookmark service is that it allows you to store your web bookmarks in a centralized place. Newer services add a social aspect by allowing you to categorize your links by tags and then view all the links that people have marked with the same tag.
To design such a service we need to answer our four questions.
- What are the resources?
- What are the representations?
- What methods are supported at each resource?
- What status codes could be returned?
What are the resources for a bookmarking system? Each bookmark we add should be a resource. In addition, aggregations of bookmarks should also be resources. Here are some example resources:
- A single bookmark
- All the bookmarks I've ever created
- All the bookmarks I've ever created for some keyword
- My 20 most recent bookmarks
- The 20 most recent bookmarks for a specific keyword, across all users
- The 20 most recent bookmarks, across all users
- A list of all the keywords used by a user
- The 20 most popular keywords used by everyone
There are many more aggregate resources we may want to build, but these examples should be enough to get us going.
What we want to do here is scan the list repeatedly looking for similarities. You'll note that the first resource in the list is a single bookmark. Most of the rest are aggregations of bookmarks. Only the keyword resources aren't covered so far, so we will make them their own kind of resource. In total we have three kinds of resources:
- Bookmark Resource
- A single bookmark and associated information, such as when the bookmark was created.
- Bookmark Collection Resource
- A collection of individual bookmarks grouped together.
- Keyword List Resource
- A list of keywords.
Since we now know the types of resources we will be dealing with, let's define the URI space that those resources will be living in.
It sounds so simple, doesn't it? "Let's define the URI space." How about, "Let's go fall off a log." Well, it actually isn't that difficult.
We'll need one or two guiding principles to help us. One principle we'll follow is using the built-in hierarchy building that the URI "path" component allows. That is, we'll use the path portion of the URIs in our system to reflect the hierarchy of our resources. Conversely, if we have resources that are non-hierarchical we'll use either query parameters or Matrix URIs.
In our system, each user's bookmarks belong to that user, so for the path component of our URIs, the user should be higher in the hierarchy (that is, to the left of the path) than the user's bookmarks. If bookmarks are categorized by tags, then the bookmarks would appear below (that is, to the right of) the tag in the path. In the following table the URIs listed are just relative URIs. We will flesh out the rest of the URI later.
|URI||Type of Resource||Description|
||Bookmark||A single bookmark for "user".|
||Bookmark Collection||The 20 most recent bookmarks for "user".|
||Bookmark Collection||All the bookmarks for "user".|
||Bookmark Collection||The 20 most recent bookmarks for "user" that were filed in the category "tag".|
||Bookmark Collection||All the bookmarks for "user" that were created in a certain year [Y] or month [M].|
||Keyword List||A list of all the "tags" a user has ever used.|
The user "all" is a special user. The aggregate of all the users in the system is
represented by the user "all". That is,
all/bookmarks/ is a Bookmark Collection
of the 20 most recent bookmarks added to the system across all users.
Now for the second question, "What are the representations?"
Here we don't have to get too creative, as an XML vocabulary for bookmarks already exists. XBEL is the XML Bookmark Exchange Language. It was designed by the Python XML Special Interest Group. Using an existing popular format will allow greater opportunities for reuse and remixing of our service once it is up and running.
Let's look at XBEL and see how it fits in with our service. First, we'll review some regular XBEL documents and then discuss what we need to add or remove to accommodate our service. Here is an example XBEL document containing a single bookmark.
<?xml version="1.0"?> <!DOCTYPE xbel PUBLIC "+//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML" "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd"> <xbel version="1.0"> <bookmark href= "http://www.xml.com/pub/a/2005/02/09/xml-http-request.html"> <title>Very Dynamic Web Interfaces</title> <desc>Using XMLHttpRequest to build dynamic web interfaces. </desc> </bookmark> </xbel>
Example: XBEL Document for a single bookmark
Note that most of the elements in an XBEL document are optional. Here is the simplest XBEL document containing just a single bookmark.
<xbel version="1.0"> <bookmark href= "http://www.xml.com/pub/a/2005/02/09/xml-http-request.html" /> </xbel>
Example: Shortest XBEL Document for a single bookmark
To simplify, I'll drop the XML declaration and doctype on future examples. Here is a more complicated example with nested folders of bookmarks:
<xbel version="1.0"> <folder> <title>XML.com</title> <folder> <title>Web Services</title> <bookmark href= "http://webservices.xml.com/pub/a/ws/2005/01/12/salz.html"> <title>Freeze the Core</title> <desc>Rich Salz explains why the web services core should be frozen. </desc> </bookmark> <bookmark href= "http://www.xml.com/pub/a/2005/01/12/saml2.html"> <title>SAML 2</title> </bookmark> </folder> <bookmark href= "http://www.xml.com/pub/a/2005/02/09/xforms.html"> <title>Top 10 XForms Engines</title> </bookmark> </folder> </xbel>
Example: XBEL Document for a bookmark collection
It appears that XBEL has most of the features we need for our bookmarking service. We can use an XBEL document with a single bookmark entry as the representation of a Bookmark Resource. We can also use an XBEL document with multiple bookmarks as the representation for a Bookmark Collection.
There are only two things missing from XBEL. The first is a place for the URI of the
Bookmark Resource for each bookmark in a Bookmark Collection. Each
<bookmark/> element has an href attribute that's the bookmark URI. In
addition to that information, we also want to store the URI of the Bookmark Resource.
Luckily XBEL has a spot for additional metadata for each bookmark. You can add
<metadata/> elements to the
<info/> element. The
XBEL specification requires that the
<metadata/> element have an "owner"
attribute that is unique, preferably a URI, that points to a description of the metadata
added. Using the extensibility of the metadata element we can add a place for the
URI of the
Bookmark Resource for each bookmark.
<xbel version="1.0"> <bookmark href= "http://www.xml.com/pub/a/2005/02/09/xml-http-request.html"> <title>Very Dynamic Web Interfaces</title> <info> <metadata owner= "http://example.com/documentation/xbel/edit" > http://example.com/jcgregorio/bookmark/12 </metadata> </info> <title>Very Dynamic Web Interfaces</title> <desc>Using XMLHttpRequest to build dynamic web interfaces. </desc> </bookmark> </xbel>
Example: Extended XBEL Document for a single bookmark
In this example, we extended an XBEL Document for a single bookmark. An XBEL Document
collection of bookmarks can be extended in the same way, by adding
<metadata/> elements to all of the
The URI we chose for the "owner" attribute is just a stand-in; we wouldn't really deploy this service by pointing to the example.com domain. Before we go public with the service we'll need to secure a place for our documentation and update that URI to point there.
The second thing missing from XBEL is a place to hold category information. Again the "metadata" element is appropriate for this information. Here is an example of an XBEL document being extended to include the category tag. We allow multiple tags separated by whitespace as element content for such a "metadata" element.
Example: Extended XBEL Document for a single bookmark
The only thing we don't have is a representation for keywords. We only need something simple:
<tags> <tag>dogs</tag> <tag>cats</tag> <tag>pets</tag> </tags>
Example: Keyword List Document
On to the third question, "What methods are supported at each resource?"
Now we need to decide which methods are supported by which resources and which
representations they will use. The Bookmark Resource represents a single bookmark,
using an XBEL document with just a single bookmark in it to represent such a resource.
retrieve the representation of a Bookmark Resource, we
GET it. To update a
bookmark, we will
PUT the information to the Bookmark Resource. To remove a
The operations on Bookmark Collection Resources include
GET to retrieve a list
of all the bookmarks in that collection, in the form of an XBEL document. And we'll
POST an XBEL document with just a single bookmark to the Bookmark Collection
Resource in order to add another bookmark to that collection.
That gives us the following types of resources and the methods they support:
||Extended XBEL Document for a single bookmark||Get a bookmark.|
||Extended XBEL Document for a single bookmark||Update a bookmark.|
||n/a||Delete a bookmark.|
||Extended XBEL Document for a bookmark collection||Get a collection of bookmarks.|
||Extended XBEL Document for a single bookmark||Add a bookmark to a collection.|
||Keyword List Document||Get a list of keywords.|
Have we maintained idempotency for our
GET on a
Bookmark Resource, or a Bookmark Collection Resource, or a Keyword List Resource doesn't
change either the Bookmark, the Bookmark Collection, or the Keyword List Resource.
Another check is to ensure that all of the methods that aren't
PUTing a new representation and
Bookmark can definitely change the state of the server. Creating a new Bookmark with
POST also changes the state, so none of them could be done by
GET. We're good so far.
The only status code we will be explicitly looking for is
201 Created when a
new bookmark is created by POSTing to the Bookmark Collection Resource. Besides that
well-written client should have good default handling for 2xx status codes for success,
for redirection and 4xx codes for errors.
Let's look at some examples of what we would expect to see "on the wire" for our protocol.
We will for now assume that our service is hosted at http://example.com. To get the
bookmarks for the user "jcgregorio" we do a
http://example.com/jcgregorio/bookmarks/recent/. What is returned will be an Extended
document with the last 20 bookmarks.
GET /jcgregorio/bookmarks/recent HTTP/1.1 Authorization: Basic ZnJpZW5kczpvbmt1 User-Agent: curl/7.10.3 (win32) libcurl/7.10.3 Host: example.com Accept: application/xbel+xml
And we get the following response:
HTTP/1.1 200 OK Date: Sun, 13 Feb 2005 04:12:33 GMT Server: Apache/2.0.46 (Red Hat) Last-Modified: Sat, 12 Feb 2005 04:02:59 GMT ETag: "444338-263-d71fc2c0" Content-Length: 611 Content-Type: application/xbel+xml <xbel version="1.0"> <bookmark href= "http://webservices.xml.com/pub/a/ws/2005/01/12/salz.html"> <title>Freeze the Core</title> <metadata owner = "http://example.com/documentation/xbel/edit" > http://example.com/jcgregorio/bookmark/1 </metadata> <desc>Rich Salz explains why the web services core should be frozen. </desc> </bookmark> <bookmark href= "http://www.xml.com/pub/a/2005/01/12/saml2.html"> <title>SAML 2</title> <metadata owner= "http://example.com/documentation/xbel/edit" > http://example.com/jcgregorio/bookmark/2 </metadata> <desc>Federated Identity</desc> </bookmark> </xbel>
Note that each bookmark has an extra "metadata" element that points to a Bookmark Resource for that bookmark.
To create a new bookmark, we
POST an XBEL document with a single bookmark to
the appropriate Bookmark Collection.
POST /jcgregorio/bookmarks/ HTTP/1.1 Authorization: Basic ZnJpsd5kczpvbmx5 Content-Type: application/xbel+xml Host: example.com <xbel version="1.0"> <bookmark href= "http://www.xml.com/pub/a/2005/02/09/xforms.html"> <title>Top 10 XForms Engines</title> <metadata owner= "http://example.com/documentation/xbel/tags" > XML XForms </metadata> <desc>Review of available implementations.</desc> </bookmark><xbel version="1.0"> </xbel>
The server could then respond with:
HTTP/1.1 201 Created Date: Sat, 12 Feb 2005 09:12:13 GMT Server: Apache/2.0.46 (Red Hat) Content-Length: 611 Content-Type: application/xbel+xml Location: http://example.com/jcgregorio/bookmark/3
Note that the server returned the status code of
201 Created and a
Location: header with the URI of the newly created Bookmark Resource.
What We Haven't Specified
Let's spend a minute covering the facets of our protocol that we haven't specified.
We haven't specified an authentication mechanism for our protocol. That's
because HTTP provides support for discovering the allowed authentication mechanisms
WWW-Authenticate: header. This is a generic, and extensible, mechanism
provided by HTTP to enumerate allowable authentications, and there's no advantage
to either route around it or hard code a specific authentication mechanism into our
We haven't specified a compression mechanism for our protocol. XML is fairly verbose, and we could probably gain some speed by specifying that the files be transferred compressed. We haven't done that because, again, HTTP supports a generic and extensible mechanism for specifying when compression is allowed and what are the allowed types of compression.
At the beginning of the Examples section I said “assume that our service is hosted at http://example.com.” That meant that all of the URIs for our system simply started with "http://example.com”. It also assumed that we would publish the structure of our URIs into the bookmarking service. That's some pretty serious hand waving. What I've glossed over is the whole idea of service discovery, which I'll cover in a future article.