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 defn
s 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.