rest

A RESTful ESME API

The Enterprise Social Messaging Experiment (ESME) project grew out of a collaboration in the SAP world and is now a project in the Apache Incubator. The project itself is an interesting and inspiring demonstration of the organizing power of so-called web 2.0 tools, which allowed the project to go from an idea to an Incubator project in about half a year through the efforts of a wide-spread group of individuals, many of whom have never met. I've been on the sidelines of the project, looking on, but I've been fascinated by some conversations that have taken place around the API.

The ESME API is often described as a "REST" API or a "RESTful" API. "REST" refers to the design principle of Representational State Transfer. Wikipedia has a good overview. REST is often seen as an alternative to RPC APIs, or "Remote Procedure Call" APIs. At root, the difference as described by Wikipedia is that RPC is about telling an application to do something while REST is about changing the state of the resources of an application.

The upshot is that RPC can be thought of as interfacing in verbs, while REST can be thought of as interfacing in nouns. To grossly oversimplify, let's pretend that I'm updating my location in the application http://www.foobar.com/.

In RPC I would do something like

POST HTTP request http://www.foobar.com/api/update_location?lat=1234&long=5678

In the context of a REST API, I would do something like

PUT or POST HTTP request http://www.foobar.com/location?lat=1234&long=5678

A subtle distinction to be sure, but let's look at the difference. In the RPC version, we see the verb in the URI. There is a separate URI for every possible action we might want to take with regards to a location (update_location, get_location, create_location, delete_location). In a REST API, the resources of the program are addressed directly (that is, we send the request to the same URI that we would use to display our location in a web browser), and the "verb" that we want to apply to the resource is embedded in the HTTP request. REST is a very HTTP-oriented design approach, but it is an approach that makes sense because HTTP is a protocol for handling resources. The Wikipedia page has more on this, and may very well contradict me, as I am by no means an API expert!

To get to the point, what would an ESME REST API look like? Let's first look at the current ESME API, which is described as "REST", but which I think we can safely conclude is actually RPC.

http://code.google.com/p/esmeproject/wiki/REST_API_Documantation

Note how each API command is a verb. This is the hallmark of an RPC API. (ESME is part of a tradition here. The Twitter "REST" API is also primarily written in an RPC style, where the verb is part of the path of URI and is not assumed based on the HTTP method. The Twitter API does assume that a GET HTTP request maps to a read except when they have also have a specific "show" verb, but now I'm just nit picking.)

There is nothing wrong with this, and RPC is actually more in line with enterprise API design standards than a REST API, but I'd like to get at what a real ESME REST api would look like. I provide here the current ESME API method along with the REST equivalent that comes to mind.

I'm listing arguments here as URL-encoded, but they could easily be form-encoded, XML, JSON or all of the above. On the REST side, where a portion of the URL is in all caps, this would be substituted by the unique ID of that particular resource. This is, of course, not a well-thought-out proposal, but rather a suggestion to illustrate what a more RESTful API might look like.

Current (RPC)

REST

GET /api/status GET api/sessions
(It might make sense for this to return only the current session, but in theory it would return all sessions that the current session is allowed to access, so for an administrator, it might return all open sessions. An individual session would be accessed at GET api/sessions/SESSIONID.)
POST /api/login
token=API_TOKEN
POST api/sessions?token=API_TOKEN
GET /api/logout DELETE api/sessions/SESSIONID or
DELETE api/sessions?session=SESSIONID
(get SESSIONID from api/sessions)
GET /api/get_msgs GET api/users/USERID/messages
(get USERID from api/session)
GET /api/wait_for_msgs GET api/users/USERID/messages (long-poll?)
GET api/messages/MESSAGEID
Gets a particular message.
POST /api/send_msg
message=messagebody
via=client
tags=tags
metadata=XML_data
replyto=message_id
POST api/messages?message=MESSAGE_BODY&via=CLIENT&tags=TAGS&metadata=XML&replyto=MESSAGEID
PUT api/messages/MESSAGEID (payload the same as POST)
DELETE api/messages/MESSAGEID
GET /api/get_following GET api/users/USERID/followees
GET /api/get_followers GET api/users/USERID/followers
POST /api/follow
user=id_of_user
POST api/users/USERID/followees/USERID2 or
POST api/users/USERID/followees?user=USERID2
POST /api/unfollow
user=id_of_user
DELETE api/users/USERID/followees/USERID2 or
DELETE api/users/USERID/followees?user=USERID2
GET /api/all_users GET api/users
GET /api/get_tagcloud
numTags=optional_no_of_tags
GET api/tags
(This doesn't really seem like an appropriate API method. It should really return all of the tags, or user-specific tags (GET api/tags/USERID) and let the front-end decide what to do with it.)
GET /api/get_tracking GET api/users/USERID/tracks
POST /api/add_tracking
track=text
POST api/users/USERID/tracks?track=TEXT_TO_TRACK
POST /api/remove_tracking
trackid=id_of_tracking_item
DELETE api/users/USERID/tracks/TRACKID
GET /api/get_conversation
conversationid=Conversation_id
GET api/conversations/CONVERSATIONID
GET /api/get_actions GET api/users/USERID/actions
(Actions probably don't make sense outside of the context of a specific user.)
POST /api/add_action
name=name
test=trigger
action=action
POST api/users/USERID/actions?name=NAME&test=TEST&action=ACTION
POST /api/enable_action
id=action_id
enabled=true|false
PUT api/users/USERID/actions/ACTIONID?enabled=true|false
(This is actually a general outlet to update any attribute of an action, including whether or not it is enabled.)
POST /api/delete_action
actionid=action_id
DELETE api/users/USERID/actions/ACTIONID

One point to note is that most HTTP clients do not currently support
the "PUT" or "DELETE" methods, so these have to be simulated
through POST methods with an extra parameter. I think that because of the close mapping to resource verbs, is worth using these methods in
the specification and defining the simulation method for the entire API
separately.

The above is based on a rough object hierarchy as follows:

  • ESME API instance (api/)
    • Sessions (api/sessions)
    • Users (api/users)
      • Messages posted by a user (api/users/USERID/messages)
      • Users followed by a user (api/users/USERID/followees)
      • Users following a user (api/users/USERID/followers)
      • Trackers belonging to a user (api/users/USERID/tracks)
      • Actions belonging to a user (api/users/USERID/actions)
    • Messages (api/messages)
    • Tags (api/tags)
    • Conversations (api/conversations)

Each of these bullets represents a set of objects. The resource representing an individual object lives at api/objects/OBJECTID. For example, api/sessions/SESSIONID. As much as is reasonable, one would expect to be able to GET (read), POST (create), PUT (update/amend), or DELETE (delete) any individual member of each of these object sets. Going through each of these objects to ask what it would mean to create, read, update, or delete that object may reveal holes in the existing API, some of which I have filled in above.

Syndicate content