Show Me the Code
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
<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
elements to the
<info/> element. The XBEL specification requires
<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
<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 for a collection of bookmarks can be
extended in the same way, by adding
<metadata/> elements to all
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, and we're using an XBEL
document with just a single bookmark in it to represent such a
resource. To retrieve the representation of a Bookmark Resource, we
GET it. To update a bookmark, we will
the information to the Bookmark Resource. To remove a bookmark,
The operations on Bookmark Collection Resources
GET to retrieve a list of all the bookmarks in
that collection, in the form of an XBEL document. And
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
GETs are non-idempotent.
PUTing a new
DELETEing a Bookmark can definitely
change the state of the server. Creating a new Bookmark
POST also changes the state, so none of them could be
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 any well-written client
should have good default handling for 2xx status codes for success,
3xx 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 latest
bookmarks for the user "jcgregorio" we do a
GET on http://example.com/jcgregorio/bookmarks/recent/.
What is returned will be an Extended XBEL document with the last 20
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
Location: header with the URI of the newly created Bookmark
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 via the
This is a generic, and extensible, mechanism provided by HTTP to
enumerate allowable authentications, and there's no advantage in
trying to either route around it or hard code a specific
authentication mechanism into our protocol.
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.
- Change to Search Options
2007-09-21 16:21:25 JNeumann
- Question on resource URI
2007-01-04 12:32:15 PauloMerson
- Accessing multiple ressources using GET
2006-02-06 13:50:00 mawi100
- Accessing multiple ressources using GET
2007-01-19 12:19:00 aronr
2005-03-04 22:20:10 Mike Dierken
2005-03-04 07:30:41 velebak