Because I’m basing my service on an existing one, it’s fairly easy to figure out the pa- rameters of the data set. If what follows is confusing, feel free to flip back to “del.icio.us: The Sample Application” in Chapter 2 for an overview of del.icio.us.
The del.icio.us site has four main kinds of data: user accounts, bookmarks (del.icio.us calls them “posts”), tags (short strings that act as metadata for bookmarks), and bun-
dles (collections of tags for a user). The web site and the web service track the same data set.
Unlike an S3 bucket, or a user account on my map service, a del.icio.us user account is not just a named list of subordinate resources. It’s got state of its own. A del.icio.us account has a username and password, but it’s supposed to correspond to a particular person, and it also tracks that person’s full name and email address. A user account also has a list of subordinate resources: the user’s bookmarks. All this state can be fetched and manipulated through HTTP.
A bookmark belongs to a user and has six pieces of state: a URI, a short and a long description, a timestamp, a collection of tags, and a flag that says whether or not it’s public (the previous chapter’s “custom place” resource has a similar flag). The client is in charge of specifying all of this information for each bookmark, though the URI and the short description are the only required pieces of state.
The URIs in users’ bookmarks are the most interesting part of the data set. When you put a bunch of peoples’ bookmarks together, you find that the URIs have emergent properties. On del.icio.us these properties include newness, a measure of how recently someone bookmarked a particular URI; “popularity,” a measure of how many people have bookmarked that URI; and the “tag cloud,” a generated vocabulary for the URI, based on which tags people tend to use to describe the URI. The del.icio.us web site also exposes a recommendation engine that relates URIs to each other, using a secret algorithm.
I’m not going to do much with the emergent properties of URIs, properties that account for much of del.icio.us’s behind-the-scenes code. My implemented service will have a notion of newness but it won’t have popularity, tag clouds, or recommendation algo- rithms. This is just so I can keep this book down to a manageable size instead of turning it into a book about recommendation algorithms.
Tags have only one piece of state: their name. They only exist in relation to bookmarks —and bundles, which I haven’t described yet. A bundle is a user’s decision to group particular tags together. A user with tags “recipes,” “restaurants,” and “food,” might group those tags into a bundle called “gustation.” I’ll show the RESTful design of bun- dles, just for completeness, but I won’t be implementing them when it comes time to write code.
At this point I know enough about the data set to create the database schema. I create an empty database called bookmarks_development in my MySQL installation, and put this data in the file db/migrate/001_initial_schema.rb, shown in Example 7-1.
Example 7-1. The bookmark database schema as a Rails migration
class InitialSchema < ActiveRecord::Migration # Create the database tables on a Rails migration. def self.up
# The 'users' table, tracking four items of state
# plus a unique ID.
create_table :users, :force => true do |t| t.column :name, :string
t.column :full_name, :string t.column :email, :string t.column :password, :string end
# The 'bookmarks' table, tracking six items of state, # plus a derivative field and a unique ID.
create_table :bookmarks, :force => true do |t| t.column :user_id, :string
t.column :uri, :string
t.column :uri_hash, :string # A hash of the URI. # See book text below. t.column :short_description, :string
t.column :long_description, :text t.column :timestamp, :datetime t.column :public, :boolean end
# This join table reflects the fact that bookmarks are subordinate # resources to users.
create_table :user_bookmarks, :force => true do |t| t.column :user_id, :integer
t.column :bookmark_id, :integer end
# These two are standard tables defined by the acts_as_taggable # plugin, of which more later. This one defines tags.
create_table :tags do |t| t.column :name, :string end
# This one defines the relationship between tags and the things # tagged--in this case, bookmarks.
create_table :taggings do |t| t.column :tag_id, :integer t.column :taggable_id, :integer t.column :taggable_type, :string end
# Four indexes that capture the ways I plan to search the # database.
add_index :users, :name add_index :bookmarks, :uri_hash add_index :tags, :name
add_index :taggings, [:tag_id, :taggable_id, :taggable_type] end
# Drop the database tables on a Rails reverse migration. def self.down
[:users, :bookmarks, :tags, :user_bookmarks, :taggings].each do |t| drop_table t
end end end
I’ve used Ruby code to describe five database tables and four indexes. I create the corresponding database schema by running this command:
$ rake db:migrate
Resource Design
In Chapters 5 and 6 I had a lot of leeway in turning my imaginary data set into resources. The idea for my map service came from the Google Maps application with its image tiles, but I took it off in another direction. I added user accounts, custom places, and other features not found in any existing map service.
This chapter works differently. I’m focusing on translating the ideas of del.icio.us into the Resource-Oriented Architecture. There are lots of ways of exposing a data set of tagged bookmarks, but I’m focusing on the ones del.icio.us actually uses. Let’s start by taking a look at what the del.icio.us web service has to offer.
The del.icio.us web service is a REST-RPC hybrid service, described in English prose at http://del.icio.us/help/api/. The web service itself is rooted at https://api.del.icio.us/ v1/. The service exposes three RPC-style APIs, rooted at the relative URIs posts/, tags/, and bundles/. Beneath these URIs the web service exposes a total of twelve RPC functions that can be invoked through HTTP GET. I need to define RESTful resources that can expose at least the functionality of these three APIs:
First, the posts/ API, which lets the user fetch and manage her bookmark posts to del.icio.us:
• posts/get: Search your posts by tag or date, or search for a specific bookmarked URI.
• posts/recent: Fetch the n most recent posts by the authenticated user. The client may apply a tag filter: “fetch the n most recent posts that the authenticated user tagged with tag t”.
• posts/dates: Fetch the number of posts by the authenticated user for each day: perhaps five posts on the 12th, two on the 15th, and so on. The client may apply a tag filter here, too.
• posts/all: Fetch all posts for the authenticated user, ever. The client may apply a tag filter.
• posts/update: Check when the authenticated user last posted a bookmark. Clients are supposed to check this before deciding to call the expensive posts/all.
• posts/add: Create a bookmark for a URI. The client must specify a short descrip- tion. It may choose to specify a long description, a set of tags, and a timestamp. A bookmark may be public or private (the default is public). A client may not book- mark the same URI more than once: calling posts/add again overwrites the old post with new information.
• posts/delete: Deletes a user’s post for a particular URI.
Second, the tags/ API, which lets the authenticated user manage her tags separately from the bookmarks that use the tags:
• tags/get: Fetch a list of tags used by the authenticated user.
• tags/rename: Rename one of the authenticated user’s tags. All posts tagged with the old name will now be tagged with the new name instead.
Finally, the bundles API, which lets the authenticated user group similar tags together. • tags/bundles/all: Fetch the user’s bundles. The resulting document lists the bun-
dles, and each bundle lists the tags it contains.
• tags/bundles/set: Group several tags together into a (possibly new) bundle. • tags/bundles/delete: Delete a bundle.
That’s the web service. As I mentioned in Chapter 2, the service only gives you access to your own bookmarks and tags. The del.icio.us web site has social features as well, and I’m going to steal some of those features for my design.
Here are some interesting “functions” exposed by the del.icio.us web site but not the web service:
• /{username}: Fetch any user’s bookmarks.
• /{username}/{tag}: Fetch any user’s bookmarks, applying a tag filter.
• /tag/{tag-name}: Fetch bookmarks tagged with a particular tag, from all users. • /url/{URI-MD5}: Fetch the list of users who have bookmarked a particular URI. The
{URI-MD5} happens to be the MD5 hash of the URI, but from the average client’s point of view that’s not important: it’s an opaque string of bytes that somehow identifies a URI within the del.icio.us system.
• /recent: Fetch the most recently posted bookmarks, from all users. The del.icio.us home page also shows this information.
Now that I know what the service has to do, arranging the features into resources is like working a logic puzzle. I want to expose as few kinds of resources as possible. But one kind of resource can only convey one concept, so sometimes I need to split a single feature across two kinds of resource. On the other hand, sometimes I can combine multiple RPC functions into one kind of resource, a resource that responds to several methods of HTTP’s uniform interface.