designing a hypermedia API w/ JSON

2010-05-18 @ 08:44#
this post is another installment in a series of posts on designing and implementing hypermedia APIs.

in previous posts i've talked about the importance of designing hypermedia APIs through the use of a media-type that contains not just the required data elements, but also the necessary link semantics to allow clients to understand how to execute queries (possibly using filter parameters) and how to write data including adding, updating, and deleting content on the server. i've also singled out some commonly-used registered media-types as hypermedia types (HTML, SMIL, etc.) and others as devoid of hypermedia semantics (XML, JSON, etc.).

before continuing, i'm going to take a short pause to make sure i highlight an important aspect of this whole series: the advantage of the hypermedia design pattern.

warning: off-topic digression up ahead!

why are we doing this anyway?

one of the key points in this series has been to emphasize to clients more than what data clients can read and write, but also how to tell clients what it takes to perform those tasks. typically, applications written to run "stand-alone" or meant to run in a closed environment get this information from the source code itself. this makes sense when programs need not interact with other components (servers, etc.) or when external components are controlled by the same development team that writes the client code. in those cases, keeping the semantics of reading and writing between parties in sync is a well-defined task and the only variables in the process are those set out by the very team in charge of the code.

however, in widely distributed networks (e.g. the Internet) where autonomous teams are building various parts of the system, the task of keeping everyone in sync with regards to read/write semantics is much harder. this is especially true when the execution environment must remain up and running 24x7, even during upgrades or modifications. these same challenges can occur in large organizations where teams operate independently and/or are physically separated. in those cases, one way to reduce the need for lock-step co-ordination is to encode the read/write rules in the mesage payloads themselves. and that's where hypermedia APIs come in. each response to a request can contain all the instructions needed to allow the client to make additional requests (including requests to write data). even when the semantics change, clients receive the new information via message payloads rather than code updates. also, in well-implemented systems clients that have not yet received the latest payloads can continue to run within the network since it's the client that drives the interactions, not the server.

the best-known example of this working in the wild is the HTML media type and the common Web browser. but these are not the only possible examples. by adopting the same principles of hypermedia API design, desktop applications can be programmed to understand the read/write semantics encoded in proprietary message payloads. for most developers, it's handy to simply use the work of browser vendors who have spent years building a robust state-machine client that knows how to handle a wide range of media types (including images, plug-ins, code-on-demand, etc). but, when the common browser has limiations (security, flexibility, inability to render important proprietary formats, etc.), building a custom state-machine client is always a viable option.

in fact, savvy web develoeprs can even build custom state-machines that "live" in the browser itself. clients that understand a media type, know the read/write semantics, and can render the data as required. they are sometimes referred to as Ajax applications (although the "X" usually stands for XML and that is not always the format used).

and that was my transition paragraph...

List Management via JSON-H

if you've been reading along in this series, you know that early on i identified a simple set of list management operations to be implemented through a Web API. in this installment, i'll show how to implement the same functionality by designing a hypermedia JSON payload.

in keeping w/ previous examples, i'll design a single message format for all operations. one that can encapsulate all the details needed for describing the full list, individual items in the list, and standard operations on the list including adding entries, updating them, and removing them. it can also be used to handle requests and responses for some pre-defined queries including remaining open items, items due today, and items within a date range.

all these read/write semantics are encoded in a single message using the JSON data format. it's what i'm calling today: JSON-H. not JSONH or JSONP or JsonML. just JSON-H.

it's all about the links

in order to design hypermedia using the JSON format, i need a standard way to express hypermedia links in the responses. to accomplish this i've adopted the same approach outlined in RESTful Web Services Cookbook by Subbu Allamaraju in 3.6. How to Design JSON Representations (caveat emptor: i am listed as a contributor to the book). the format i use for representing links in JSON is the following:

  {"link" :
    {"href": "http://www.example.org/path/item", "rel": "semantic-keyword"}
  }

i use a similar pattern to represent links that also support named parameters (e.g. query filters):

  {"link" : 
    {"href": "{query-link}", "rel": "today" },
    "start-date": "",
    "stop-date": "" 
  }
  

ok, with those details out of the way, it's time to design a hypermedia type for the list management service using JSON

the JSON list management hypermedia API

first, here's a minimal response. all valid responses will have at least this reply:

  // the minimum response
  { "list" :
    { "link" : "href" : "{collection-uri}", "rel" : "list"}
  }

note that there will be a list element that contains as least one link that contains the URI for the list itself. in keeping with other implementations, i am not including the actual URI for the list; that depends upon the server implementing the media-type. also, keep in mind it might not even be an HTTP URI. FTP, XMPP, or some other application protocol might work just fine for retrieving the list.

writing data (e.g. adding a new entry or editing and existing entry) would accept a message payload that looks like this:

  // typical write request
  { "item" :
    {"title" : "...", "description" : "...", "date-due" : "...", "completed" : false}
  }

as per previous blog posts, the URI for the add operation is the {collection-uri} and the URI for the update operation is the {item-uri}. the delete operation also uses the {item-uri}. as you might guess, these operations need to be bound to protocol method names. for example, in the initial blog post outlining the service, HTTP methods POST, PUT, and DELETE, are bound to the Add, Update, and Delete operations. other protocols will need their own bindings.

as already alluded to here, queries are handled using links and links w/ named arguments. the details of the URI are not important here, either; just the link relation keywords that clients can look for and use to determine how to assemble requests to be sent to the server.

(
  "queries" : 
  [
    {
      "link" : {
        "href": "{query-link}",
        "rel": "today" 
      } 
    },
    {
      "link" : {
        "href": "{query-link}",
        "rel": "open" 
      } 
    },
    {
      "link" : {
        "href": "{query-link}",
        "rel": "date-range" 
      },
      "start-date": "",
      "stop-date": "" 
    } 
  ]
}

notice that i designed my hypermedia JSON to place all the queries in a collection. this will help the client locate them in the response messages. i also group all the individual items in a collection for the same reason. i'll show those shortly.

but what about errors?

i've gotten a few questions recently on how to best design responses when an error occurs. again, Subbu's book covers this well in 3.13. How to Return Errors and i use the same basic pattern:

{
  "error" : {
    "link" : {
      "href": "{error-details-link}",
      "rel": "error-details" 
    },
    "message" : "...",
    "description": "...",
    "code" : "..."
  }
}

the full monty

ok, that's all for each of the elements of my JSON-H implementation of the list management API. now here's a complete rendering of a media type response encoded in the JSON format. this media-type needs a name (don't they all) and i call this one application/list.man.json. of course, i should take the time to properly register this custom media-type so that others will be able to use it, too.

// complete JSON-H implementation of list manager
{
  "list" : {
    "link" : {
      "href": "{collection-uri}",
      "rel": "list" 
    },
    "items" : [
      {
        "link" : {
          "href": "{item-uri}",
          "rel": "item" 
        },
        "title" : "First task",
        "description" : "start JSON implementation of the list",
        "date-due" : "2010-05-01",
        "completed" : false 
      },
      {
        "link" : {
          "href": "{item-uri}",
          "rel": "item" 
        },
        "title" : "Second task",
        "description" : "Implement JSON hypermedia task list over FTP",
        "date-due" : "2010-05-02",
        "completed" : false 
      },
      {
        "link" : {
          "href": "{item-uri}",
          "rel": "item" 
        },
        "title" : "Third task",
        "description" : "Go jogging.",
        "date-due" : "2010-05-03",
        "completed" : false 
      } 
    ],
    "queries" : [
      {
        "link" : {
          "href": "{query-link}",
          "rel": "today" 
        } 
      },
      {
        "link" : {
          "href": "{query-link}",
          "rel": "open" 
        } 
      },
      {
        "link" : {
          "href": "{query-link}",
          "rel": "date-range" 
        },
        "start-date": "",
        "stop-date": "" 
      } 
    ],
    "error" : {
      "link" : {
        "href": "{error-details-link}",
        "rel": "error-details" 
      },
      "message" : "",
      "description": "",
      "code" : ""
    } 
  }
}  

bottom line

no matter the platform or client, using a hypermedia design for your APIs makes it easier to deploy and maintain your application in a widely distributed network where you do not control all the clients and all the servers using your service.

like other media types that contain no native read/write semantics, implementing hypermedia APIs using the JSON format means you need to define and document a standard way to express read/write semantics including link elements and possible parameters for filtered queries and write operations.

finally, it's a good practice to use a standard way to represent error responses for all your media types.

and, above all, keep writing hypermedia APIs.

REST