Sunday, June 26, 2011

RESTful JSON fixtures for testing using CouchDB

 

I'm working in a distributed team at the moment where I don't have access to infrastructure available within the company's firewall. The application I'm working on has a nice RESTful API with JSON payloads for its logic layer. As I'm mostly developing the presentation layer I needed a quick method of generating something that would simulate the logic layer.

One thing that I felt was important is that I shouldn't have to mutate any settings within my presentation application in order to work either against the simulated logic layer, or the real one. It is all too easy for such settings to creep into production and break things.

I had a few thoughts on how to achieve my objectives considering Spring MVC,JAX-RS re-implementation using our existing interfaces (we're using JAX-RS in the real logic layer), writing a Jetty based servlet handler and node.js. Then I remembered...

CouchDB is a document oriented database that uses JSON as its Data Description Language (DDL). In addition CouchDB provides a RESTful JSON based API to access the database. This got me thinking that I could use sample responses from our real logic layer and enter them directly into the CouchDB database. It turns out you can using the batch interface!

I then thought that I needed to create a CouchDB service that could understand the parameters of the web service request I needed to make. These parameters are properties of my JSON objects that are otherwise unavailable to the URL format that CouchDB provides for querying. The answer here is to provide a View. Here's my view:

function(doc) { emit(doc.surname, doc) }

The surname is a property of my JSON object that I'll want to query.

I call the view the same name as my logic layer service. In my case the view's name is "getCustomerBySurname". If I query my service using CouchDB's URL convention I would issue something like:

http://localhost:5984/customerdb/_design
/customerService-1.0/_view
/getCustomerBySurname?key=%22Hunt%22

This yields a result like:

{"total_rows":1,"offset":0,"rows":[
  {"id":"285b217949d54c29c4b27adf6d000da2","key":"Hunt","value":
    {"id":1,"surname":"Hunt","firstname":"Christopher"...

Note that I'm using a convention for versioning a resource so that I can evolve its API nicely in the future - this is quite important for RESTful requests as versioning isn't something supported by anything within HTTP

I now have a web service that behaves the way the real service behaves. However, it doesn't look the same to the consumer for two reasons:

  • the result contains meta information about the query that the consumer does not expect ("total_rows" etc.); and
  • the URL we provide is not of the same form as the real logic layer service.

Transforming the JSON result

To transform the payload returned by CouchDB into something our consumer expects we create a List in CouchDB-speak. My list function looks like this:

function (head, req) {
  provides('json', function() {
    var results = [];
    while (row = getRow()) { results.push(row.value); }
    send(JSON.stringify(results));
  });
}

I've declared my list by the name of "rows". The function is focused on pulling out just the row.value property of each row and returning that back to the consumer. To access the view that I created and have it render through the list you provide a URL like this:

http://localhost:5984/customerDB/_design/
customerService-1.0/_list/rows/
getCustomerBySurname?key=%22Hunt%22

This now yields something like:

[{"id":1,"surname":"Hunt","firstname":"Christopher"...

...which is what my consumer expects.

Transforming the url

The url is transformed via Apache httpd using a RewriteRule. In order to avoid Same Origin Policy we've had to proxy requests for our presentation component and its web services anyhow. Here is the re-write rule I ended up with:

RewriteEngine On
RewriteOptions Inherit
RewriteRule ^/CustomerDB/services-1.0/rest/Customers?surname=(.*) http://localhost:5984/customerdb/_design/customerService-1.0/_view/getCustomerBySurname?key="$1" [QSA,P]

... and that's it. I now have a means of using CouchDB to provide fixtures for the purposes of development.

One last important thought...

The really interesting thing about using CouchDB for the use-case I set out to solve is that I'm really starting to wonder whether the real logic layer should be written using CouchDB... it is so simple and powerful!

 

 

No comments: