Stuart Sierra's Clojure Development Workflow
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 ofsystem
(noun)create-system
instead ofsystem
(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 theclojure-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
anddissoc
are used to add and remove pieces of the system as it is started and stopped. - Note how the
-main
function effectively duplicatesuser/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
withvalue
. - Add an Increment button to the web page and use
POST
instead ofPUT
to invoke the increment action.
- Replace all
- 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
withvalue
, 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 ofcreate
go
instead ofcreate-and-start
- Revert back to some of Stuart Sierra's naming, so that this is
in line with anyone else who's using Stuart's ideas:
- 26th November 2013:
- Simplify things by not using lein ring and by having the same code for production and development.