Skip to content

Instantly share code, notes, and snippets.

@sparkertime
Last active December 21, 2015 02:49
Show Gist options
  • Save sparkertime/6238120 to your computer and use it in GitHub Desktop.
Save sparkertime/6238120 to your computer and use it in GitHub Desktop.
A response to http://blog.softwarecurmudgeon.com/blog/2013/08/13/compojure-is-letting-us-down/, a well written and clearly thought-provoking article from my friend William.

I understand where your pain is coming from, but I think the cure is worse than the disease. Your currying approach introduces "argument noise" that is prevalent amongst dependency injection techniques. Namely, a change in the dependencies of one low-level method percolates all the way to the very top of your application. So in your example, every bit of shared configuration bubbles to the very top of your application like so:

(defn create-bid [db]
  (fn [request]
    (transact db tx-data)

(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)))

This is fine for an application whose actions only talk to the DB. What about when you introduce Redis? RabbitMQ? Log4J? I suppose you could pass along a map of keys to shared global state, but now you have the same drawbacks as vars (every method could interact with every bit of global state) without the advantages. I don't see how your currying approach improves things, but perhaps I am missing something.

I think an alternative approach that uses with-redefs provides those same benefits without the pain. You can define defaults for a particular shared resource or value (even if that default is nil) and then override that in your particular context. (See Chas Emerick's post at http://cemerick.com/2011/10/17/a-la-carte-configuration-in-clojure-apis/ for a good discussion of the two techniques).

It's probably inaccurate to think of Clojure as "post-global-state." It provides mechanisms for intelligently managing global state. I think it's important to apply those mechanisms with pragmatism.

@sparkertime
Copy link
Author

I share that concern about explicit dependencies - that's what I tend to put my var declarations in dedicated namespaces. Then references to that namespace are my explicit dependencies and are simpler to track than references to a db variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment