Implicit REST Services with Lift
Do you ever have those days, when you wake up and become unhappy with the "code you wrote last year":http://blog.getintheloop.eu/2010/03/29/multi-format-rest-services-with-lift/ ? Today was one of those days (technically speaking it was yesterday, as I went to bed thinking this, but still). There simply must be a better way. So, im going to blame this post on "Ross":http://twitter.com/dridus and "Jon-Anders":http://twitter.com/jteigen . Those two are truly to blame for my interest in type-classes and compiler voodoo... anyway, I digress...
_DISCLAIMER: The following post makes heavy use of Scala implicit conversions and type system foo. Its not big, or clever, but it is fun. Before someone points it out, yes, there is a far simpler route to implement something similar. This was ultimately an experiment :-) _
I write a lot of services. These services do a lot of different things and more often than not need to be published in a bunch of formats. In this way, its often easy to end up with one of two things: * Accidental complexity * Code duplication or blurred lines between logic process and media representation
In this post i'm going to show you one possible method of avoiding the second problem, but, subjectively, the first problem may seem better or worse depending on your familiarity with the Scala type system.
Plan of Attack
Ok, so how are we going to do this? Well, as you may or may not know, Scala has this awesomeness called "implicit conversions":http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 - these can be used to get the compiler to convert type A to type B when it only has A, but requires B. This is useful because the Scala compiler will use the appropriate implicit conversion that it finds within the given lexical scope; essentially meaning that you can "inject" different behaviour at different points during compile time. With this in mind, its a concept that matches quite closes to multi-format services. That is, I have type A from my logic or domain process and I need type B for the representation and delivery of a service.
We will be making a very small sample application that
The Book Store
Before we get into the utter type-madness, let us define the basic domain classes we'll be using:
I think you'll agree this is super basic. Basically its just a fake bookstore that has a super limited stock of only Harry potter and Scala books. Strange, but true. Anyway...
Define the Service Types
We'll need some traits to glue this stuff together and provide the right syntax.
ReturnAs[A, B] - This structure is basically used to mediate each format from its input to its output; in and of itself it does not do a lot. The companion object here is actually where the good stuff is: the implicit method
f2ReturnAs takes a function
A => B and converts it to a ReturnAs type. This is going to be handy when it comes to writing the implementation as we'll be able to say
Book => LiftResponse etc.
The next structure is the
Return[A] type and its nested type
As[B]. The Return type will be used as an explicit helper within the service implementations, so the API would be Return() via the companion object apply method. In addition to the single parameter, this method also has an implicit parameter of:
implicit f: ReturnAs[A, B] which will be made available to use from within the implementation call via "Scala context bounds":http://stackoverflow.com/questions/2982276/what-is-a-context-bound-in-scala. This context bound will be expanded to plain type parameter T, together with an implicit parameter so that we can "recapture" the correct return type within the implementation.
With the plumbing in place, one needs to actually provide implementation of the various A => B type conversions. In this exceedingly small sample, im only going to be working with List[Book], so to keep this sample as simple as possible these are the implicit conversions for making an XML and plain text service:
I wont labour the details too much here as this is pretty standard LiftResponse stuff that you can find in my other articles. That being said, there is an interesting point of note, and that is the type signatures:
Here we use a type projection to specify the return type - this is pretty sick I think you'll agree.
With the media type conversions in place, the only thing that is actually left is within the service creation element is the writing of the method implementations.
So this is important. This is where we use the context bound that we talked about earlier comes into play in expanding to a type parameter and an implicit parameter which allows us to write these rather nice looking methods wrapped in a the Return objects apply method without needing to specify any specific types.
Last but certainly not least is writing this awesomeness up into the HTTP implementation. To do that we are simply going to use Lift's REST DSL that needs a return type that is some subtype of LiftResponse. Of course, here we have those so we get a rather nice looking implementation:
To test this out, all you need to do is visit /bookshop/books to get the text representation and /bookshop/books.xml to get the XML version. Hope you've enjoyed this article, you can find full source available to download from here
Find more information like this within my book, Lift in Action