Stuart Sierra's Clojure Development Workflow



Reloading the REPL

Clojure development is nice and dynamic, but there are times when it's not as dynamic as you might like and then it can take time to restart your environment, get all your development utilities in place and get your application re-loaded so that you can continue to work on it from a known clean state.

Stuart Sierra has been working on improving this.

I came across his tools.namespace a while ago, and it's been very useful.

Stuart has been taking things further recently, coming up with an approach to designing applications that plays nicely with dynamic development and which allows very fast reloading of applications during development.

He's described this in a recent blog post, in his Clojure in the Large talk at Clojure/West, and in Relevance Podcast Episode 32.

I've produced an example project that uses Stuart's approach, and I describe that here.

My Demo Application

Clojure Workflow Demo, at https://github.com/simon-katz/clojure-workflow-demo is a simple project that uses Stuart's ideas.

Some Name Changes

I've used:

  • the-system instead of system (noun)
  • create-system instead of system (verb)

If you prefer Stuart's names, the repository's first commit uses those. (But there are other changes since the first commit — see the updates at the end of this post.)

A Use of defonce

I define the-system using defonce. This means I can make changes to utility functions in user.clj and recompile it without losing the reference to the system.

(If you use the repository's first commit, note that it uses def, not defonce.)

Details of the Demo Application

The application consists of the following namespaces:

  • In the dev directory:
    • user
      • As described in Stuart's blog post.
      • Note the utility function at the end of the file for making changes to the instance of the domain model.
  • In the src directory (omitting the clojure-workflow-demo. prefix):
    • domain.domain-stuff
      • A simple application domain, consisting of a single number with a read function and an increment function.
    • system
      • As described in Stuart's blog post.
      • Note how into and dissoc are used to add and remove pieces of the system as it is started and stopped.
      • Note how the -main function effectively duplicates user/go.
    • web.server
      • Functions to create, start and stop a web server.
      • Used by system.
    • web.web-page
      • Functions to create a handler.
      • Used by system.

Stuart mentions that with his approach functions need to take extra arguments, and that with the use of lexical closures these extra arguments disappear from most code. You can see an example of this in the create-handler and create-handler* functions in web.web-page, where domain-model is the extra argument. (It's not obvious, but GET and PUT are macros that create Ring handler functions, so there are lexical closures here.)

Developing and Running the Demo Application

Have a play:

  • Start a REPL, and evaluate (reset).
  • Point a web browser at http://localhost:7601/.
  • Press the Increment button, and note that the value increases.
  • Evaluate (reset) again and refresh the browser. Note that the value returns to 0. You now have a system that is re-using the same port as before.

Also try this sequence and observe what happens at each stage:

  • Evaluate (reset).
  • Evaluate (inc-value).
  • Refresh the browser to see the effect.
    • See a '1'.
  • Evaluate (stop).
  • Evaluate (inc-value).
    • This is ok — our instance of the domain model still exists.
  • Refresh the browser.
    • Could not connect, because there's no web server.
  • Evaluate (start).
  • Refresh the browser.
    • OK, because we have a web server now.
    • And we see a '2'.

The file test/clojure_workflow_demo/web_helpers/manual_setup.sh contains a curl command for incrementing the value. Try it, and refresh the browser.

Evaluate (stop)and then from a command window, try:

  • lein run

UPDATES

  • 20th June 2013: Create a new branch in the repository with the following changes:
    • Replace all value-in-domain-model with value.
    • Add an Increment button to the web page and use POST instead of PUT to invoke the increment action.
  • 2nd August 2013:
    • The video of Stuart Sierra's talk at Clojure/West is now available, so add a link to it.
    • In the repository, merge the branch mentioned in the 20th June 2013 update into the master branch. In this post, replace all value-in-domain-model with value, and describe using the Increment button.
  • 25th November 2013:
    • Revert back to some of Stuart Sierra's naming, so that this is in line with anyone else who's using Stuart's ideas:
      • init instead of create
      • go instead of create-and-start
  • 26th November 2013:
    • Simplify things by not using lein ring and by having the same code for production and development.