My Clojure Workflow Reloaded Reloaded
Published on Sep 26, 2013 by dix.
Several months ago Stuart Sierra wrote a post on the Relevance blog which I
found very interesting. In it, he described a way of doing Clojure development
that I wanted to try. At first glance, I was interested in it only because it
gave me a good workflow for testing within the Clojure repl. I love Test-Driven
development and I have always had problems practicing it well in Clojure. I had
previously tried two approaches. To begin with, I tried to make do with simply
running lein test
in the shell when I needed to run tests. The context switch
coupled with the slow JVM start up time made this approach unfeasible.
Moving on from this, I attempted to run tests in the REPL without the use of
Stuart’s tools.namespace
library. I learned after a few hours of attempting
that I was simply too dumb. I have no idea how code loading works in Clojure. I
do not know how to reliably reload code and all its dependencies. I never really
knew what the state of the system was at any given time. Once I started making
use of the workflow suggested by Sierra I was actually able to reliably and
quickly run tests in the repl. This drastically improved my feelings about
Clojure development.
A lot of my problems are likely related to the fact that I don’t really know what I’m doing. I understand some of the fundamentals around functional programming and LISP. But I do not understand the specific Java-ness of Clojure. I have never written Java more complex than is necessary to maintain the Braintree Java Client Library. I’m certain that for someone with more familiarity with Java, it would be very easy to start up an instance of Jetty when initializing the system and then setting up the ring handler within. I don’t know how to do that. For this reason, I was unable to full commit to this technique. I had to make a compromise. Here is how I init the system and start the app.
(defn system [] (let [db-url (or (System/getenv "DATOMIC_URL") "datomic:mem://sayc-app")] {:db-url db-url :handler (sayc-app.web/create-handler db-url) :web-port (or (try (Integer/parseInt (System/getenv "PORT")) (catch NumberFormatException _ nil)) 8080) })) (defn start [system] (d/create-database (:db-url system)) (migrate/migrate (d/connect (:db-url system))) (assoc system :server (ring/run-jetty (:handler system) {:port (or (:web-port system) 8080) :join? false}) :db-connection (d/connect (:db-url system))))
Since I don’t really understand jetty
I have to rely on ring/run-jetty
to
initialize the server. This means there will be some hidden elements of global
state. I wish I could get rid of it and I don’t think this is exactly what
Stuart Sierra had in mind, but it works for me.
Other than the benefit of being able to reliably and quickly run tests, there was a different huge benefit. Constructing an application this way leads you to really consider where all of your global state lies. When you initialize the database connection at such a high level, it is much harder to rely on it deep within the application. This is what led me to the approach I outlined in my previous post. When you initialize this complex system at such a high level, you have to think much harder about what needs access to what.
It is however not all sunshine and roses. This way of working is much harder than just hacking out some Clojure code. It is a quite heavyweight solution. There are parts of it that I found useful that I would like to separate out. I love the good code reloading and I love the ease with which I could run tests. However, designing an application in this way is definitely overkill for the simple toy app I’ve been working on. Granted, I don’t think anyone would have ever thought otherwise. I’m certain that when one is waist-deep in an extremely complicated application this workflow is invaluable.