This is my Blog.

It's full of opinions on a wide range of stuff.

Multi-format REST services with Lift

Note: This subject is discussed in much greater detail within my upcomming book Lift in Action, more information will shortly be published on manning.com

The Goal

So what are we actually trying to achieve here? We'll use the twitter.com API as an example as its a good illustration in this instance. Consider the following URLs:

http://api.twitter.com/users/timperrett.xml

and

http://api.twitter.com/users/timperrett.json

Its the exact same content, but just presented in different formats. This is very nice, and also very helpful as often you might be in a situation where you need to syndicate a single API from multiple devices or platforms where toolkits may be limited. For example, working with JSON is a lot easier from Java script than it is XML (the latter is of course possible its just more verbose). In the examples above, Twitter are using the format component of the URL following the "." to determine the content type the user wants - this is of course one route, and some services may want to add the additional stipulation of a specified accepts and content-type headers in the requests so the server is sure its serving the right content.

The interesting problem here is that if you have the same content, you really only want one point of access for the logic. You simply dont want to be maintaining two set of logic - only the actual code that handles differences in presentation should alter between calls right?

Implementation Pattern

Typically, when I want to create web services in my Lift application I break out my dispatchers into separate objects related to their concerns. This way it keeps things nice and tidy. So, in our example, i'll be wiring up the following services:

  LiftRules.statelessDispatchTable.append {
    case Req("apps" :: Nil, "json", GetRequest) => 
      ApplicationServices.json.list
    case Req("apps" :: Nil, "xml", GetRequest) => 
      ApplicationServices.xml.list
  }

For those not familiar with Lift's dispatching mechanism this will expose the following URLs:

  • /apps.json
  • /apps.xml

All pretty simple so far. As you can see from the code listing above, I have broken my application services out into a singleton object called ApplicationServices which has fields for each content type im going to be presenting. Without further ado lets take a look at the definition of the apps listing service:

object ApplicationServices extends Loggable {
  .....
  protected def _list(mediaAction: List[String] => LiftResponse) =
    () => tryo(mediaAction(Applications.list.map(_.name)))
  .....
}

Ok so this is pretty cool. Im using function passing to let each subsequent definition define how it builds the LiftResponse sub-type; in this instance that'll be JsonResponse for JSON and XmlResponse for XML.

JSON Listing

def list = _list((apps) => 
  JsonResponse(compact(JsonAST.render(("applications" -> apps)))))

XML Listing

def list = _list((apps) => XmlResponse({
  <applications>
    {apps.map(a => <application name={a} />)}
  </applications>
}))

So you can see that each presentation version is only dealing with how it needs to create the response... its not re-computing the list of applications or such.

Syntax Sugar

Within the overall definition I define the formats as inner-objects and assign them to public fields in order to achieve the nice ApplicationServices.xml.list notation:

object ApplicationServices extends Loggable {
  val json = Json
  val xml = Xml

  protected def _list(mediaAction: List[String] => LiftResponse) =
    () => tryo(mediaAction(Applications.list.map(_.name)))

  object Json {
    // definitions here
  }

  object Xml {
    // definitions here
  }
}

Round Up

Thats pretty much all there is too it. You can leverage Scala's extremely powerful features to create your web services and keep code LOC to a minimum. Over and out.

blog comments powered by Disqus