Why Are We Settling for this in Clojure
Published on Aug 13, 2013 by dix.
I did a quick Github search to find instances of people passing around global database connections. It didn’t take long for me to find a bunch of examples of this.
(def ^:dynamic ^java.sql.Connection *db* nil) (def *db* {:subprotocol "postgresql" :subname "//localhost:5432/blogjr" :username "blogjr" (def ^{:doc "The binding contains the database connection settings."} *db* {:classname "com.mysql.jdbc.Driver"})
Why are we okay with this? This isn’t the post global state future we were promised. If you are using this technique, there are a limited number of options to swap out your database. You can either redefine a dynamic variable or using with-bindings. I had a conversation on Twitter recently with one of the few people I know who has actually been paid to write Clojure about this topic:
A dynamic var is as injectable as I’ve ever needed. Can set a default and override using with-bindings/with-redefs
I think in most applications, a database connection is a perfectly acceptable piece of global state, esp. for pooled conns.
I’m quick to believe that there is an issue with what I’m about to propose. It’s very likely in fact. I have never done serious work in Clojure. The largest app I’ve written in Clojure is maybe 300 LoC. So take this with the world’s largest grain of salt.
We’re going to take advantage of one of the primitive functional programming techniques – currying. Currying is a process whereby a function with multiple arguments is transformed into a function which takes one argument returning a function takes a single argument and so on… For example,
(defn foo [a b c] (+ a b c)) (defn foo-curried [a] (fn [b] (fn [c] (+ a b c)))) (= (foo 1 2 3) (((foo-curried 1) 2) 3))
So we write our actions like this:
(defn create-bid [db] (fn [request] (transact db tx-data)
In starting the web server, I create the database connection and initialize the web handler like so.
(defn build-routes [db] (defroutes routes (POST "/bids" [] (actions/create-bid db)) (resources "/"))) (defn create-handler [db] (build-routes db) (-> (var routes) (keyword-params/wrap-keyword-params) (nested-params/wrap-nested-params) (params/wrap-params)))
I like this. I much prefer it to dealing with the global database connection. Am I missing something? Is there a reason that this isn’t preferable? Is it overkill?
I likely feel uncomfortable with the status quo because there are a lot of things left in Clojure I don’t understand. I don’t really get what it means to reassign these global variables. I don’t know what the best way is to do this. I do understand how to pass arguments to functions. I’ve been doing that since I first touched a Lisp in my first year of college 7 years ago. If I understood things better, I would probably feel more comfortable with this than I do, but I don’t know much.