Sunday, December 13, 2009

Quicksilver and REST

Let's talk about some REST a la Quicksilver shall we? In my Quicksilver primer post I touched on what it's all about and its automatic dependency injection capabilities. Today we touch on another one of its core features - REST. If you are not familiar with the concept I encourage you to look into first before reading this post.

Let's set the stage. Imagine you built a simple informational design patterns site that explains design patterns. It's been in production for awhile now using OO best practices like service objects to be the intermediary between internal api calls and the data layer. This site has been using Quicksilver because they wanted a powerful flexible framework to one day use AOP and REST in addition to Automatic Dependency Injection just in case they needed to open up their API to other programs. Fast forward to now, they've been getting requests to open up their API via web services. They can use Quicksilver's REST support to do this easily

To understand how QS provides a simple solution to this, let's explore a possible alternative. They could develop soap based web services that consumes their service layer. Not a bad approach, but possibly time consuming and if done incorrectly can also lead to duplicate code since the methods will need to understand how to return json, xml, or a custom format. Also each web service endpoint would be exposed through a cfc - /path/to/webservice/DesignPatterns.cfc?wsdl and call methods off that.  Ugly ugly ugly. Doing it this way is unorganized and resources are all over the place with crud-type methods names - saveThis(), updateThat(), getThis() etc. Get my drift? How does Quicksilver solve this?

Let's dig into the code that we are about to REST'ify.

We've got a design pattern service with one method getCreationalPatterns(), it simply returns an array of strings whose values are pattern names. Here's the code:

Simple enough right?

  • We've got a singleton annotation at the top that tells Quicksilver that this should be handled as a singleton. This is part of automatic dependency injection baby.
  • getCreationalPatterns() simply returns an array. In the real world this could (and should) get as complex as you wanted to, perhaps delegating to a DAO or gateway or even another service but for this example let's KISS.
Good, now take a look at the unit test written for this. Again keeping it simple, let's assert that we are getting five elements back. Using MXUnit, let's write it:

Test passed. Sweet.

Ok so let's now expose this method as a RESTful endpoint that returns a json representation of creational patterns shall we? Let's not create a new component and go through soap based web services, it too much work :) Let's use Quicksilver. How? Use two, count em, two simple annotations in your service object.

  1. @url - this annotation is used to specify your url pattern. 
  2. @httpMethod - possible values are GET, POST, DELETE, PUT. You can use multiple methods separated by commas.
So here's the updated code:

Lines 8-11 baby, that's all you need. Now when I crack open my browser and hit:
We get a json representation like so:

When opened looks like:

See how easy that was? We've just opened up our API via restful web services to our consumers quickly and easily.When this URI is called from an ajax request, consumers are free to use the json representation of the creational patterns however they wish.

Lets take a closer look at that URI:


  • We've mapped an HTTP GET call to the url "/design/patterns/creational". Talk this out. Map a HTTP GET to the /design/patterns/creational resource and give it to me in json.
  • @httpMethod GET is the annotation to map an http method a function
  • api.cfm is the required cfm template that must exist for CF to parse the uri. This can be named whatever you want. index.cfm, rest.cfm, I just arbitrarily named it api.cfm. Again, as with any request that must go ColdFusion, you must go through a cfm page. In Quicksilver, however, that cfm page does not need to exist. Just the fact that its in the URL is enough, we'll take care of the rest. In our example, api.cfm is not a real file.
  • After api.cfm comes the path we set in our @url annotation.
  • Automatic Serialization - Quicksilver will automatically serialize arrays, structs and queries to json for you and since our return type is an array, automatic serialization is kicks in.
  • By default the return of this call also returns an HTTP status code of 200. You have full control over the status codes as well.
Easy right? Imagine this uri being called via ajax call from jQuery, ExtJS or whatever ajax feature you use. You'll get a json representation back.

Lastly, you've heard use say again and again that the Quicksilver framework does not get in your way and keeps your codebase HIGHLY testable. We're not just saying this to sound cool, we'll prove it. Lets re-run our unit test even after we  activated REST.

Look at that :) Test still passed. There's your proof. Quicksilver doesn't inject itself into your code rather it hangs out outside of it. It allows you to implement code and test without the framework in your way and we mean it. How? Via annotations baby! Read more about in the Quicksilver primer post if your confused.

side note: Just FYI - Quicksilver also supports custom representations prepended with whatever extension you want - .extjs? .pdf? .myCoolExtension?. Also allows you to have FULL control on HTTP status codes that are returned as well as the ability to send variables via the URL, http raw post, form post etc.  No problem. We'll get to that in future blog posts, promise. Or check out Brian's Quicksilver blog. Link at the end of the article.

So to wrap up, I hope you now see how Quicksilver is as unobtrusive as possible. We REST'd our application with no added components, no messy soap web services, no duplicate code all while remaining organized and  highly testable. No framework artifacts riddling your awesome code. How about that? A framework that actually cares about risk management.


Quicksilver Blog - entries by the one and only Brian Carr. Check it out!

No comments: