A Lazy Sequence

Configuration middleware

When you are developing a web application in Clojure, there are likely to be libraries (such as database access libs) that require configuration such as connection information to be provided. If the library provides a way to dynamically bind the configuration data, we can use Ring middleware to simplify our applications. I assume you already know about Ring. I’m going to use Moustache for my routes here, but the principle applies to Compojure – and presumably noir – as well. For a database library I’ll show an example using Clutch (the Clojure CouchDB api), again this also applies to other libraries.

Lets imagine a simple moustache app:

(def my-app 
    [] (delegate home)
    [slug] (delegate page slug))

In this example, home and page do some lookups into a CouchDB database, and return some html, for example:

(use '[ring.util.response :only [response]]
    '[com.ashafa.clutch :only [with-db get-view]])

(defn get-page-from-db
    [slug]
    (-> (with-db "example-db" (get-view "site" :page {:key slug}))
                 first
                 :content))    

(defn get-page-from-db-2
    [slug]
    (-> (get-view "example-db" "site" :page {:key slug})
        first
        :content))

(defn page 
    [req slug]
    (response (get-page-from-db slug)))

This simplified handler and database access function (and its variant) highlight the problem: the connection information (in this case just the string "example-db") is coupled with the code the does the request. Newer Clojure programmers may try hoisting the (with-db …) above the defns but this doesn’t work due to the binding semantics of dynamic scopes.

Ring middleware presents an answer to this problem. If we create a middleware that will set the (with-db …) for us on each request, then hoist the definition out of the data access code and specify it only once. Here is an example:

(defn clutch-with-db
  "Wraps the routes in a clutch with-db binding"
  [app database]
  (ƒ [req]
    (com.ashafa.clutch/with-db database (app req))))

(def my-app2
   (app 
     (clutch-with-db "example-db")

     [] (delegate home)
     [slug] (delegate page slug)))

Easy!

In addition to hoisting the configuration out of the data access code, we can now trivially use a different database for two sub apps that use the same app definition but accesses a different database, in this case two blogs: one with serious content, and another with humorous cats:

(def simple-blog (app …))

(def blogs 
    (app 
       ["funny-cats" &] (clutch-with-db simple-blog "cats-blog")
       [&] (clutch-with-db simple-blog "serious-blog")))

Finally, this also means you can write test harnesses that work off separate databases without fear.

20 January 2012