Chapter 2
Rapid Feedback Cycles with Clojure

Hopefully you found Chapter 1 to be a good overview of Clojure and why it is an ideal choice for your development language. Clojure's approach to functional programming, laziness, mutability, and other concepts make it a good candidate for web development. You've likely chosen Clojure for many of these reasons, but an important part of being successful with any language is being productive.

There are multiple aspects that factor into your productivity with a given language, but perhaps one of the most important aspects is feedback. Decreasing the amount of time spent waiting on feedback is a critical part of improving your productivity in any language. Shortening the feedback cycle is a problem that is addressed not only by specific languages and the tooling for that language but by development methodologies in general. Ideas like agile programming, continuous integration, and test-driven development have shorter feedback cycles as one of their goals.

Of course, high-level approaches to developing projects like these are outside the scope of this book. This chapter dives into how you can be more productive with Clojure in your day-to-day work—specifically, how you can decrease the length of your feedback cycle with Clojure both by how you approach development with Clojure and by leveraging the tools provided by Clojure and its ecosystem.

REPL-DRIVEN DEVELOPMENT

A REPL is a tool found in many programming languages and is commonly used for quick feedback while developing. Even if you haven't heard the term REPL before, you've almost certainly used one before without knowing it. Any time you've used a command line, you've used a REPL.

When you hear the term REPL-driven development, you may think of it similarly to other development methodologies, such as test-driven development. REPL-driven development, however, is not so much a strict set of practices and guidelines for development as it is a recognition that the REPL is a useful tool to leverage regardless of your development process. Even if you are already following a methodology like test-driven development, leveraging the REPL to speed your feedback leads to more productivity. This section covers the basics of working with the REPL in a Clojure project as well as how you can integrate a REPL with some tools you may already be familiar with.

Basic REPL Usage with Leiningen

If you are familiar with Clojure, then you are also likely very familiar with one of its more popular build tools: leiningen. Otherwise, you should visit http://leiningen.org/ and make sure you have leiningen installed on your machine before you continue. Let's go ahead and create a new leiningen project so you can play with the REPL (lein is the script used within leiningen).

lein new compojure chapter2
cd chapter2
lein repl
nREPL server started on port 61908 on host 127.0.0.1 - nrepl://127.0.0.1:61908
REPL-y 0.3.5, nREPL 0.2.6
Clojure 1.7.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_45-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e
user=>

Now you've created a new Clojure project named chapter2 and started a REPL within that project. Already, the REPL has conveniently provided some clues as to how you can quickly start developing the application. You can see that there are useful tools available for looking up Clojure documentation, Clojure source code, and even Java documentation. Let's go ahead and try a few of them out.

user=> (doc map)
-------------------------
clojure.core/map
([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
  Returns a lazy sequence consisting of the result of applying f to
  the set of first items of each coll, followed by applying f to the
  set of second items in each coll, until any one of the colls is
  exhausted.  Any remaining items in other colls are ignored. Function
  f should accept number-of-colls arguments. Returns a transducer when
  no collection is provided.
nil
user=> (find-doc #"map.*parallel")
-------------------------
clojure.core/pmap
([f coll] [f coll & colls])
  Like map, except f is applied in parallel. Semi-lazy in that the
  parallel computation stays ahead of the consumption, but doesn't
  realize the entire result unless required. Only useful for
  computationally intensive functions where the time of f dominates
  the coordination overhead.
nil

Not only can you easily find the documentation for functions and macros that you already know but you can use find-doc to look up things in the documentation for which you have forgotten the function or macro name. In this example, we couldn't remember what the parallel version of map was called, but passing a simple regular expression to find-doc helped us quickly find what we needed. We were able to use source to quickly look and see how pmap works under the hood:

user=> (source pmap)
(defn pmap
  "Like map, except f is applied in parallel. Semi-lazy in that the
  parallel computation stays ahead of the consumption, but doesn't
  realize the entire result unless required. Only useful for
  computationally intensive functions where the time of f dominates
  the coordination overhead."
  {:added "1.0"
   :static true}
  ([f coll]
   (let [n (+ 2 (.. Runtime getRuntime availableProcessors))
         rets (map #(future (f %)) coll)
         step (fn step [[x & xs :as vs] fs]
                (lazy-seq
                 (if-let [s (seq fs)]
                   (cons (deref x) (step xs (rest s)))
                   (map deref vs))))]
     (step rets (drop n rets))))
  ([f coll & colls]
   (let [step (fn step [cs]
                (lazy-seq
                 (let [ss (map seq cs)]
                   (when (every? identity ss)
                     (cons (map first ss) (step (map rest ss)))))))]
     (pmap #(apply f %) (step (cons coll colls))))))
nil

You can see from the line rets (map #(future (f %)) coll) that each invocation of the function passed to map is being run in a separate thread. Given the overhead of creating new threads, pmap probably isn't useful unless the function you are applying is intensive. Of course, the documentation states that, but being able to dive in and understand why is incredibly useful.

Lastly, look at the javadoc function. As mentioned in Chapter 1, Clojure's interoperability with Java means that you often use Java objects and methods directly rather than use a Clojure interface. It is incredibly useful to be able to quickly open relevant documentation in these situations. In the source for pmap, you can see the use of the Java class Runtime to get the number of processors, so this example looks up the Java documentation for that class (see Figure 2.1).

Snapshot of a Java documentation, which shows use of the java class Runtime to get the number of processors on the machine.

Figure 2.1

user=> (javadoc Runtime)
true

As you can see the, the javadoc function automatically launched the configured default browser and loaded the documentation for the specified class. This will prove invaluable as you are developing Clojure code, especially if you are coming from a background with limited Java experience.

Remote REPLs with nREPL

When you start a REPL with lein, there is actually a lot more going on behind the scenes than may be immediately obvious. Rather than just starting a single REPL process, lein is actually starting a REPL server in the background and is also starting a client to connect to that server. In the previous section, you may have noticed the following line printed when we started our REPL.

nREPL server started on port 61908 on host 127.0.0.1 - nrepl://127.0.0.1:61908

First, you can see that something called an nREPL server is starting, which uses the Clojure library tools.nrepl (https://github.com/clojure/tools.nrepl). The tools.nrepl library provides an easy interface for starting, stopping, and interacting with a REPL over the network (the n in nREPL stands for network). So you can see that when we started our REPL earlier with lein an nREPL server was started automatically on a randomly picked port. When we are working in our REPL, we aren't actually evaluating our code in the client process; we are submitting it to the nREPL server for evaluation and displaying the result. It's easy to verify this by connecting two REPLs to the same server. First an initial REPL is started and a hello world function is created:

lein repl :start :host localhost :port 60000
nREPL server started on port 60000 on host localhost - nrepl://localhost:60000
...
user=> (defn hello-world [name]
  #_=>   (println (format "Hello %s!" name)))
#'user/hello-world

Then a second REPL pointed at the same server is started and the hello world function is called:

lein repl :connect localhost:60000
Connecting to nREPL at localhost:60000
...
user=> (hello-world "Nick")
Hello Nick!

As you can see, the function defined in the first REPL is also available in the second REPL instance because they are connected to the same server. In practice, connecting to a single REPL server simultaneously from multiple clients while developing may not be particularly useful. In fact, it may even lead to confusion if one client is changing code in use by another client. Simply having a REPL that runs remotely is rather useful, though. For one thing, you can start a REPL in the background without a client and connect to it on demand without needing to start a server again. Running lein repl :headless :host localhost :port 60000 starts a REPL server without a client whereas lein repl :connect localhost:60000 connects to that server.

In addition to simply running a REPL server in the background, you can also embed a REPL into your applications. This allows you to debug your application interactively while it is running. Starting a REPL server programmatically with the tools.nrepl library is simple:

user=> (use '[clojure.tools.nrepl.server :only (start-server stop-server)])
nil
user=> (start-server :port 7888)
#'user/server

Generally speaking, though, it's not wise to run a REPL server in a production application. Because that would pose potential security concerns, you generally want to run a REPL server in your application only while you are developing.

The next section of this chapter describes an example of configuring an application to start a REPL server automatically during development.

REPL Usage with a Real Application

At this point we've covered the REPL and some simple applications. Now, let's build a simple application to demonstrate leveraging the REPL in a more realistic scenario. Earlier you created a lein project named chapter2 by running lein new compojure chapter2. This created a new project using the compojure (https://github.com/weavejester/compojure) project template. If you open src/chapter2/handler.clj in your editor of choice, you should see the following:

(ns chapter2.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/not-found "Not Found"))
(def app
  (wrap-defaults app-routes site-defaults))

At the moment this application is just a sample hello world app. But, let's build a to-do list application with a simple REST based interface. For now, the to-do list will be fairly simple. Create the file src/chapter2/core.clj with the following contents:

(ns chapter2.core)
(def id-atom (atom 0))
(defn next-id [] (swap! id-atom inc))
(def tasks (atom (sorted-map)))
(defn get-tasks
  "Get all tasks on the to-do list"
  []
  @tasks)
(defn add-task
  "Add a task to the to-do list. Accepts a string describing the task."
  [task]
  (swap! tasks assoc (next-id) task))
(defn remove-task
  "Removes a task from the to-do list. Accepts the id of the task to remove."
  [task-id]
  (swap! tasks dissoc task-id))

You now have an extremely simple to-do list that stores the tasks on the list in memory. Of course, with a real application you'd want to use some sort of persistent storage for your tasks. Before you modify src/chapter2/handler.clj to define the REST API for the to-do list, let's test it out from the REPL. First, make some modifications to the lein project to make development easier. Tell lein what the main class of the application is by adding :main chapter2.core, and add some dependencies to make development and testing easier. Edit your project.clj so that it contains:

(defproject chapter2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-json "0.4.0"]]
  :plugins [[lein-ring "0.9.7"]]
  :ring {:handler chapter2.handler/app}
  :main chapter2.core
  :profiles
  {:dev {:dependencies [[clj-http "2.0.1"]
                        [javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.3.0"]]}})

Now, when you start a REPL, you'll notice that lein automatically puts you in the chapter2.core namespace. Let's test out the simple to-do list from the REPL.

lein repl
...
chapter2.core=> (doc add-task)
-------------------------
chapter2.core/add-task
([task])
  Add a task to the to-do list. Accepts a string describing the task.
nil
chapter2.core=> (add-task "Buy more milk.")
{1 "Buy more milk."}
chapter2.core=> (add-task "Take out the trash.")
{1 "Buy more milk.", 2 "Take out the trash."}
chapter2.core=> (add-task "File taxes.")
{1 "Buy more milk.", 2 "Take out the trash.", 3 "File taxes."}
chapter2.core=> (doc remove-task)
-------------------------
chapter2.core/remove-task
([task-id])
  Removes a task from the to-do list. Accepts the id of the task to remove.
nil
chapter2.core=> (remove-task 2)
{1 "Buy more milk.", 3 "File taxes."}
chapter2.core=> (get-tasks)
{1 "Buy more milk.", 3 "File taxes."}
chapter2.core=>

You can leverage the tools you learned about earlier in the chapter like doc to see your own internal documentation, and then easily check to see how the code works. Now that we've verified this simple to-do list is working, we'll modify our compojure app to provide a REST interface for interacting with the app. The updated src.chapter2.handler is:

(ns chapter2.handler
  (:require [chapter2.core :as tasks]
            [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [ring.middleware.keyword-params :refer [wrap-keyword-params]]
            [ring.middleware.json :refer [wrap-json-response]]))
(defroutes api-routes
  (GET "/api/tasks" []
    {:body (tasks/get-tasks)})
  (POST "/api/tasks" {{task :task} :params}
    {:body (tasks/add-task task)})
  (DELETE "/api/tasks/:task-id" [task-id]
    {:body (tasks/remove-task (Integer/parseInt task-id))})
  (route/not-found "Not Found"))
(def app
  (-> api-routes
      (wrap-defaults api-defaults)
      wrap-json-response))

All you need to do now to run the application is run lein ring server-headless. That will run the application on port 3000, and you can again head to the REPL to test things out. Let's use the clj.http library as a quick and easy way to test the REST API.

chapter2.core=> (require '[clj-http.client :as client])
nil
chapter2.core=> (:body
           #_=>   (client/get "http://localhost:3000/api/tasks" {:as :json}))
{}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks"
           #_=>                {:form-params {:task "Buy milk."} :as :json}))
{:1 "Buy milk."}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks"
           #_=>                {:form-params {:task "Take out trash."} :as :json}))
{:1 "Buy milk.", :2 "Take out trash."}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks"
           #_=>                {:form-params {:task "File taxes."} :as :json}))
{:1 "Buy milk.", :2 "Take out trash.", :3 "File taxes."}
chapter2.core=> (:body
           #_=>   (client/delete "http://localhost:3000/api/tasks/2" {:as :json}))
{:1 "Buy milk.", :3 "File taxes."}
chapter2.core=>

So far we've been using a REPL outside of the application to develop, but as covered earlier, you also have the option to embed a REPL directly inside of the application. The lein ring (https://github.com/weavejester/lein-ring) you've been using to start the application makes starting an embedded REPL extremely simple. Update project.clj to tell lein ring to also start a REPL server.

(defproject chapter2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-json "0.4.0"]]
  :plugins [[lein-ring "0.9.7"]]
  :ring {:handler chapter2.handler/app
         :nrepl {:start? true
                 :port 60000}}
  :main chapter2.core
  :profiles
  {:dev {:dependencies [[clj-http "2.0.1"]
                        [javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.3.0"]]}})

Now when you run lein ring server-headless, you can connect the REPL to the running application. This allows you to directly inspect and modify the internal data structures and functions while the application is still running.

lein repl :connect localhost:60000
...
user=> (ns chapter2.core)
nil
chapter2.core=> (require '[clj-http.client :as client])
nil
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks"
           #_=>                {:form-params {:task "Buy milk."} :as :json}))
{:2 "Buy milk."}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks"
           #_=>                {:form-params {:task "Take out trash."} :as :json}))
{:2 "Buy milk.", :3 "Take out trash."}
chapter2.core=> (defn clear-all-tasks [] (swap! tasks empty))
#'chapter2.core/clear-all-tasks
chapter2.core=> (clear-all-tasks)
{}
chapter2.core=> (:body (client/get "http://localhost:3000/api/tasks" {:as :json}))
{}
chapter2.core=>

By connecting a REPL to the running application, you can create a new function for clearing out the task list and verify that the function works without having to restart the application.

Connecting Your Editor to a REPL

So far we avoided calling out any specific editors or IDEs to use while writing the Clojure application. Generally speaking, any editor is a viable candidate to use while developing Clojure. Practically speaking though, making the right choice in an editor can drastically improve your experience and shorten your feedback loop. Of course, there are the normal concerns with language support for Clojure in your choice of editor. In addition to that, choosing an editor that provides REPL support out of the box or through plugins will prove invaluable during development.

The home page for tools.nrepl (https://github.com/clojure/tools.nrepl) handily lists the editors that are known to support tools.nrepl already. As you can see, there are plugins for popular editors like Emacs, Vim, Eclipse, IntelliJ, and Atom. Unsurprisingly, you can also see the top three development environments from the 2014 State of Clojure Survey (https://cognitect.wufoo.com/reports/state-of-clojure-2014-results/): Emacs, IntelliJ, and Vim.

The benefit of connecting your editor of choice to an nREPL server is gaining much of the same functionality that we've discussed so far directly inside your editor, rather than in a separate REPL process. By connecting your editor to a REPL you can easily look up documentation, view source, and evaluate code directly from the editor.

We won't go into the specifics of setting up each editor to connect to a REPL, but instead simply recommend that you take a moment to set up the editor you are most familiar with and configure nREPL support. The plugins for nREPL support for Emacs, IntelliJ, and Vim, are cider (https://github.com/clojure-emacs/cider), cursive (https://cursive-ide.com/), and fireplace (https://github.com/tpope/vim-fireplace).

RELOADING CODE

So far we've touched on leveraging the REPL for rapid feedback while developing new Clojure code, but in the real world it's much more common during your day-to-day development that you will modify existing code, rather than writing brand new code from scratch. As you've seen, the REPL is a great tool, but it isn't necessarily a replacement for your preferred editor or IDE.

As you've made changes in your editor, you've been forced to reload your application or REPL in order to view those changes. Ideally, you can develop code normally in your editor and reload changed code in other places without interrupting your workflow. Luckily, Clojure makes this process simple for all parts of the workflow, from your REPL to your tests. In this section you'll see the different ways code reloading can smooth your development process, as well as how best to structure your code for reloading.

Reloading Code from the REPL

For the first demonstration of reloading code, let's see how you can reload code from your external REPL process to avoid having to restart the REPL server when you want to see changes that have been made outside the REPL. Before you get started with the example, update your project.clj to pull in the dependencies you'll need for examples later in this section. Update your project.clj to:

(defproject chapter2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-json "0.4.0"]]
  :plugins [[lein-ring "0.9.7"]]
  :ring {:handler chapter2.handler/app
         :nrepl {:start? true
                 :port 60000}}
  :main chapter2.core
  :profiles
  {:dev {:dependencies [[clj-http "2.0.1"]
                        [javax.servlet/servlet-api "2.5"]
                        [org.clojure/tools.namespace "0.2.11"]
                        [ring/ring-mock "0.3.0"]]}})

Now, load your to-do list application from the previous section into the REPL.

lein repl
...
chapter2.core=> (add-task "Buy Milk.")
{1 "Buy Milk."}
chapter2.core=> (add-task "Take out the trash.")
{1 "Buy Milk.", 2 "Take out the trash."}
chapter2.core=> (get-tasks)
{1 "Buy Milk.", 2 "Take out the trash."}

Add some functionality to your to-do list application. Ideally, a to-do list application allows you to keep multiple lists of tasks that you want to track, rather than just a single list of all tasks. In your editor, update src/chapter2/core.clj to allow keeping separate lists of tasks.

(ns chapter2.core)
(def id-atom (atom 0))
(defn next-id [] (swap! id-atom inc))
(def tasks (atom (sorted-map)))
(defn get-task-lists
  "Get the names of all created task lists."
  []
  (keys @tasks))
(defn get-tasks
  "Get all tasks on the specified to-do list."
  [list-name]
  (get @tasks list-name))
(defn add-task
  "Add a task to the specified to-do list. Accepts the name of
  the list and a string describing the task."
  [list-name task]
  (swap! tasks assoc-in [list-name (next-id)] task))
(defn remove-task
  "Removes a task from the specified to-do list. Accepts the name
  of the list and the id of the task to remove."
  [list-name task-id]
  (swap! tasks update-in [list-name] dissoc task-id))

You can now track multiple lists to add and remove tasks from. If you go back to the REPL that you opened previously, however, you can see that the changes made aren't yet reflected in the REPL, so you can still use the previous versions of your functions.

chapter2.core=> (add-task "File Taxes.")
{1 "Buy Milk.", 2 "Take out the trash.", 3 "File Taxes."}

Luckily, the Clojure require function that you are already familiar with includes a flag for reloading a given namespace. Without leaving your REPL, reload the chapter2.core namespace and see the changes that have been made in your editor:

chapter2.core=> (require 'chapter2.core :reload)
nil
chapter2.core=> (add-task "Pay electric bill.")
ArityException Wrong number of args (1) passed to:
    core/add-task  clojure.lang.AFn.throwArity (AFn.java:429)
chapter2.core=> (doc add-task)
-------------------------
chapter2.core/add-task
([list-name task])
  Add a task to the to-do list. Accepts a string describing the task.
nil
chapter2.core=> (add-task "bills" "Pay electric bill.")
{"bills" {1 "Pay electric bill."}}
chapter2.core=> (get-tasks "bills")
{1 "Pay electric bill."}

Unfortunately, using require to reload namespaces can often present problems. Simply passing :reload to require will only reload the namespace(s) specified. If you make changes to multiple files that depend on each other, you need to remember to reload all of the namespaces you changed, or you will see compilation errors. There is also the :reload-all option that you can pass to require, which will not only reload the namespace specified, but the namespaces it depends on. Even that isn't necessarily perfect though. If you change the public method signatures of a namespace, you still need to remember to reload every namespace that depended on the one you changed. There is no way for require to automatically know which namespaces need to be reloaded.

Luckily, there is a Clojure library named tools.namespace (https://github.com/clojure/tools.namespace) that you can leverage to solve these problems. You may have noticed, that tools.namespace was the dependency added to project.clj in the beginning of this section. Before we demonstrate using tools.namespace, let's add some more functionality to the application and see how simply reloading with require can go wrong. You've already updated chapter2.core to allow the application to create and track multiple task lists, but you haven't updated the API yet. Also, since you've added the ability to create multiple lists, but not the ability to remove lists, let's add that functionality as well. The function to remove lists to add to chapter2.core.clj is:

(defn remove-list
  "Delete an entire list of tasks at a time. Accepts the name of
  the list to delete."
  [list-name]
  (swap! tasks dissoc list-name))

And the new chapter2.handler.clj:

(ns chapter2.handler
  (:require [chapter2.core :as tasks]
            [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [ring.middleware.keyword-params :refer [wrap-keyword-params]]
            [ring.middleware.json :refer [wrap-json-response]]))
(defroutes api-routes
  (GET "/api/tasks/:list-name" [list-name]
    {:body (tasks/get-tasks list-name)})
  (POST "/api/tasks/:list-name" {{list-name :list-name task :task} :params}
    {:body (tasks/add-task list-name task)})
  (DELETE "/api/tasks/:list-name" [list-name]
    {:body (tasks/remove-list list-name)})
  (DELETE "/api/tasks/:list-name/:task-id" [list-name task-id]
    {:body (tasks/remove-task list-name (Integer/parseInt task-id))})
  (route/not-found "Not Found"))
(def app
  (-> api-routes
      (wrap-defaults api-defaults)
      wrap-json-response))

You've now updated both src/chapter2/core.clj and src/chapter2/handler.clj. If you reload just the handler namespace, you'll immediately see a compilation error, because the compiler won't be able to find the remove-list function that was added.

chapter2.core=> (require 'chapter2.handler :reload)
CompilerException java.lang.RuntimeException:
    No such var: tasks/remove-list, compiling:(chapter2/handler.clj:15:12)

Of course, in this specific example, you can use :reload-all in order to reload the dependencies of chapter2.handler, but rather than remembering that ourselves, let's leverage tools.namespace instead.

chapter2.core=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
chapter2.core=> (refresh)
:reloading (chapter2.core chapter2.handler chapter2.handler-test)
:ok

Behind the scenes, tools.namespace builds a graph of all the namespace dependencies in the project and uses that graph to reload everything in the correct order. You can see how useful this will be when developing larger Clojure applications, despite the current app being fairly simple. While tools.namespace is a handy tool to have, it certainly isn't a complete solution to every issue. Even the refresh function won't be able to successfully reload the project if you've accidentally introduced syntax errors into your code base during development. If you get REPL into an unknown state and you are uncertain what code is actually loaded or running, restarting your REPL completely and starting from scratch is a perfectly viable solution.

Automatically Reloading Code

So far when the act of reloading code has been a manual process. You first update the application through your editor, save the code, and then manually reload that code in your REPL. This is an incredibly useful way to rapidly get feedback about your development. In fact, this approach is so common that Clojure libraries and tools recognize this and make it even easier by removing the manual step.

Reloading a Running Application

Earlier in the chapter, you ran the REST API for the to-do list application by running lein ring server-headless. This automatically serves the API defined in src/chapter2/handler.clj on port 3000 on your local machine. In addition to serving the API though, the lein ring plugin automatically checks for changes in the local Clojure code when it receives new API requests, and reloads the application before processing those requests. Let's see this in action by expanding the functionality of your to-do list while the application is running. First, let's run the application:

lein ring server-headless
Started nREPL server on port 60000
2016-01-19 20:37:37.787:INFO:oejs.Server:jetty-7.6.13.v20130916
2016-01-19 20:37:37.818:INFO:oejs.AbstractConnector:Started
    [email protected]:3000
Started server on port 3000
...

Let's also verify that it's working the way it should using the clj-http library.

lein repl
...
chapter2.core=> (require '[clj-http.client :as client])
nil
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks/chores"
           #_=>   {:form-params {:task "Take out the trash."} :as :json}))
{:chores {:1 "Take out the trash."}}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks/chores"
           #_=>                {:form-params {:task "Mow the lawn."} :as :json}))
{:chores {:1 "Take out the trash.", :2 "Mow the lawn."}}
chapter2.core=> (:body
           #_=>   (client/get "http://localhost:3000/api/tasks/chores"
           #_=>               {:as :json}))
{:1 "Take out the trash.", :2 "Mow the lawn."}

Now, let's add some functionality to the to-do list. In addition to simply tracking a single task as a string describing the task, it might also be useful to track when a task was created. Let's start tracking when a task was created in addition to just the task. We'll create a Clojure record to represent tasks and give it a creation-time field. The new version of src/chapter2/core.clj is:

(ns chapter2.core)
(def id-atom (atom 0))
(defn next-id [] (swap! id-atom inc))
(def tasks (atom (sorted-map)))
(defrecord Task [task creation-time])
(defn now
  "Returns a java.util.Date object representing the current time."
  []
  (new java.util.Date))
(defn get-task-lists
  "Get the names of all created task lists."
  []
  (keys @tasks))
(defn get-tasks
  "Get all tasks on the specified to-do list."
  [list-name]
  (get @tasks list-name))
(defn add-task
  "Add a task to the specified to-do list. Accepts the name of
  the list and a string describing the task."
  [list-name task]
  (swap! tasks assoc-in [list-name (next-id)] (Task. task (now))))
(defn remove-list
  "Delete an entire list of tasks at a time. Accepts the name of
  the list to delete."
  [list-name]
  (swap! tasks dissoc list-name))
(defn remove-task
  "Removes a task from the specified to-do list. Accepts the name
  of the list and the id of the task to remove."
  [list-name task-id]
  (swap! tasks update-in [list-name] dissoc task-id))

While updating the to-do list application to also track task creation times, your application has continued running in the background. Let's test out the API again after making the changes.

chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks/chores"
           #_=>                {:form-params {:task "Mow the lawn."}
           #_=>                 :as :json}))
{:chores {:1 {:task "Mow the lawn.", :creation-time "2016-01-20T03:23:55Z"}}}
chapter2.core=> (:body
           #_=>   (client/post "http://localhost:3000/api/tasks/chores"
           #_=>                {:form-params {:task "Take out the trash"}
           #_=>                  :as :json}))
{:chores {:1 {:task "Mow the lawn.",
              :creation-time "2016-01-20T03:23:55Z"},
          :2 {:task "Take out the trash",
              :creation-time "2016-01-20T03:24:06Z"}}}
chapter2.core=> (:body
           #_=>   (client/get "http://localhost:3000/api/tasks/chores"
           #_=>               {:as :json}))
{:1 {:task "Mow the lawn.", :creation-time "2016-01-20T03:23:55Z"},
 :2 {:task "Take out the trash", :creation-time "2016-01-20T03:24:06Z"}}

As you can see, the new version of chapter2.core has been automatically reloaded and added tasks now also have a creation-time.

Reloading Tests

One part of the development cycle that we haven't really touched on yet is testing. Testing is a broad subject both in general and from a Clojure perspective. Because of that, there is an entire chapter of this book dedicated to testing in Clojure. Chapter 4 provides an in depth look into testing libraries, writing tests, testing strategies, and more.

This chapter focuses on the development process and how you can shorten feedback cycles with Clojure. No matter what your thoughts are on testing, it's certainly an important part of the development cycle. You can also leverage Clojure's code reloading capabilities during the testing phase of the development cycle. This section focuses briefly on leveraging code reloading as part of testing, but for more on testing see Chapter 4.

Of course, in order to dive into testing as it relates to reloading, you first have to write some tests. Let's go ahead and add some tests for the chapter.core namespace. Create the file test/chapter2/core_test.clj and add the tests there.

(ns chapter2.core-test
  (:require [clojure.test :refer :all]
            [chapter2.core :refer :all]))
(defn clear-tasks-fixture [f] (swap! tasks empty) (f))
(use-fixtures :each clear-tasks-fixture)
(deftest test-get-task-lists
  (testing "No task lists."
    (is (empty? (get-task-lists))))
  (testing "Creating task lists."
    (add-task "list1" "task1")
    (is (= ["list1"] (get-task-lists)))
    (add-task "list2" "task2")
    (is (= ["list1" "list2"] (get-task-lists)))))
(deftest test-get-tasks
  (testing "Empty task list"
    (is (empty? (get-tasks "list1"))))
  (testing "Non-empty task lists."
    (add-task "list1" "task1")
    (is (= 1 (count (get-tasks "list1"))))
    (is (= "task1" (:task (second (first (get-tasks "list1"))))))
    (add-task "list1" "task2")
    (is (= 2 (count (get-tasks "list1")))))
  (testing "Duplicate tasks are allowed"
    (add-task "list2" "task1")
    (is (= 1 (count (get-tasks "list2"))))
    (add-task "list2" "task1")
    (is (= 2 (count (get-tasks "list2"))))))

For now, you've added two fairly simple test cases. Also, when you created the project, lein helpfully created a test file automatically for the handler namespace. Since we aren't concerned with testing that part of our application at the moment, let's delete the file test/chapter2/handler_test.clj in order to focus on the tests for the core namespace. After deleting handler_test.clj you can run tests with lein test, and you should see the tests pass.

lein test
lein test chapter2.core-test
Ran 2 tests containing 9 assertions.
0 failures, 0 errors.

Now, let's examine the reloading aspect of testing. There are actually multiple tools out there, but let's use the plugin lein-test-refresh (https://github.com/jakemcc/lein-test-refresh). The first step is to add the plugin to project.clj. Update the :plugins section of project.clj to add the lein-test-refresh plugin.

(defproject chapter2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-json "0.4.0"]]
  :plugins [[lein-ring "0.9.7"]
            [com.jakemccrary/lein-test-refresh "0.12.0"]]
  :ring {:handler chapter2.handler/app
         :nrepl {:start? true
                 :port 60000}}
  :main chapter2.core
  :profiles
  {:dev {:dependencies [[clj-http "2.0.1"]
                        [javax.servlet/servlet-api "2.5"]
                        [org.clojure/tools.namespace "0.2.11"]
                        [ring/ring-mock "0.3.0"]]}})

Now, you can run the plugin with lein test-refresh.

lein test-refresh
Retrieving com/jakemccrary/lein-test-refresh/0.12.0/lein-test-refresh-0.12.0.pom
    from clojars
Retrieving jakemcc/clojure-gntp/0.1.1/clojure-gntp-0.1.1.pom from clojars
Retrieving com/jakemccrary/lein-test-refresh/0.12.0/lein-test-refresh-0.12.0.jar
    from clojars
Retrieving jakemcc/clojure-gntp/0.1.1/clojure-gntp-0.1.1.jar from clojars
*********************************************
*************** Running tests ***************
:reloading (chapter2.core chapter2.handler chapter2.core-test)
Testing chapter2.core-test
Ran 2 tests containing 9 assertions.
0 failures, 0 errors.
Passed all tests
Finished at 22:39:24.091 (run time: 1.806s)

The plugin starts up, runs the tests, and instead of exiting, continues watching your project files for changes so that as you make changes to your source code, your tests will run automatically. Let's test it out by letting lein test-refresh continue running and updating the behavior of your application slightly. Currently, your to-do list allows adding the same task to a list multiple times. The current test case explicitly tests that this behavior works. Instead, it might be more useful to ignore an attempt to add a duplicate task to a list. In the spirit of test driven development, let's update the tests to reflect the new desired behavior before you update the application. The new test/chapter2/core_test.clj is:

(ns chapter2.core-test
  (:require [clojure.test :refer :all]
            [chapter2.core :refer :all]))
(defn clear-tasks-fixture [f] (swap! tasks empty) (f))
(use-fixtures :each clear-tasks-fixture)
(deftest test-get-task-lists
  (testing "No task lists."
    (is (empty? (get-task-lists))))
  (testing "Creating task lists."
    (add-task "list1" "task1")
    (is (= ["list1"] (get-task-lists)))
    (add-task "list2" "task2")
    (is (= ["list1" "list2"] (get-task-lists)))))
(deftest test-get-tasks
  (testing "Empty task list"
    (is (empty? (get-tasks "list1"))))
  (testing "Non-empty task lists."
    (add-task "list1" "task1")
    (is (= 1 (count (get-tasks "list1"))))
    (is (= "task1" (:task (second (first (get-tasks "list1"))))))
    (add-task "list1" "task2")
    (is (= 2 (count (get-tasks "list1")))))
  (testing "Duplicate tasks are not allowed"
    (add-task "list2" "task1")
    (is (= 1 (count (get-tasks "list2"))))
    (add-task "list2" "task1")
    (is (= 1 (count (get-tasks "list2"))))
    (add-task "list2" "task1")
    (is (= 1 (count (get-tasks "list2"))))))

As soon as you save this new version of the test, you should notice how the running lein test-refresh process detects the changes and re-runs the tests, but this time with failures.

...
Finished at 22:39:24.091 (run time: 1.806s)
*********************************************
*************** Running tests ***************
:reloading (chapter2.core-test)
Testing chapter2.core-test
FAIL in (test-get-tasks) (core_test.clj:30)
Duplicate tasks are allowed
expected: (= 1 (count (get-tasks "list2")))
  actual: (not (= 1 2))
FAIL in (test-get-tasks) (core_test.clj:32)
Duplicate tasks are not allowed
expected: (= 1 (count (get-tasks "list2")))
  actual: (not (= 1 3))
Ran 2 tests containing 10 assertions.
2 failures, 0 errors.
Failed 2 of 10 assertions
Finished at 22:50:42.699 (run time: 0.032s)

Immediately you see that the tests have failed as expected. Given that the startup time of the JVM can approach 10s of seconds in some cases when calling lein test manually, this approach of automatically reloading code and running tests can greatly help your productivity. Now that you've updated the tests with the desired behavior, go ahead and fix the currently broken code. The updated add-task function looks like:

(defn add-task
  "Add a task to the specified to-do list. Accepts the name of
  the list and a string describing the task."
  [list-name task]
  (letfn [(maybe-add-task [current-list]
            (if (some #(= task (:task (second %1))) current-list)
              current-list
              (assoc current-list (next-id) (Task. task (now)))))]
  (swap! tasks update list-name maybe-add-task)))

The new function scans your list of tasks and verifies that there isn't an existing task by that name already in your task list before adding it. This approach isn't particularly elegant, but it suits this simple application, and as you can see from the updated output of lein test-refresh, it satisfies your updated test case.

...
Finished at 22:50:42.699 (run time: 0.032s)
*********************************************
*************** Running tests ***************
:reloading (chapter2.core chapter2.handler chapter2.core-test)
Testing chapter2.core-test
Ran 2 tests containing 10 assertions.
0 failures, 0 errors.
Passed all tests
Finished at 23:58:22.477 (run time: 0.048s)

Writing Reloadable Code

The final topic on reloading code relates to how you develop and structure the code you are writing, rather than the tools available for reloading it. Even if you are using intelligent tooling for reloading code, it is possible to write code that is not particularly reload friendly. The current to-do list application already falls into this trap by creating a global state in the chapter2.core namespace. Both id-atom and tasks in src/chapter2/core.clj represent a global state that is not reload friendly. Let's demonstrate what we mean via the REPL.

lein repl
...
chapter2.core=> tasks
#object[clojure.lang.Atom 0x36d0c4df {:status :ready, :val {}}]
chapter2.core=> (add-task "chores" "Mow lawn.")
{"chores"
  {1 #chapter2.core.Task{:task "Mow lawn.",
                      :creation-time #inst "2016-01-20T06:49:55.503-00:00"}}}
chapter2.core=> (add-task "chores" "Take out trash.")
{"chores"
  {1 #chapter2.core.Task{:task "Mow lawn.",
                      :creation-time #inst "2016-01-20T06:49:55.503-00:00"},
   2 #chapter2.core.Task{:task "Take out trash.",
                      :creation-time #inst "2016-01-20T06:50:02.155-00:00"}}}
chapter2.core=> tasks
#object[clojure.lang.Atom 0x36d0c4df
{:status :ready,
 :val
   {"chores"
     {1 #chapter2.core.Task{:task "Mow lawn.",
                         :creation-time #inst "2016-01-20T06:49:55.503-00:00"},
      2 #chapter2.core.Task{:task "Take out trash.",
                         :creation-time #inst "2016-01-20T06:50:02.155-00:00"}}}}]
chapter2.core=> (require 'chapter2.core :reload)
nil
chapter2.core=> tasks
#object[clojure.lang.Atom 0x779599ec {:status :ready, :val {}}]

In this example you add some tasks to your task list and then see that reloading your namespace erases the list of tasks, because (def tasks (atom (sorted-map))) is re-evaluated when you reload the namespace. When developing in the REPL, this behavior isn't particularly harmful or confusing. If code is being reloaded outside of the REPL, though, this can be problematic. If you add multiple tasks to your task list via the REST API, and then change the documentation on one of your functions in chapter2.core, you will be unpleasantly surprised when the next API request returns an empty task list because the namespace was reloaded. One potential solution is to use defonce, which only evaluates if the specified var doesn't exist. However, while that solution helps the problem when reloading with require, it does not when reloading with tools.namespace. When reloading with tools.namespace, you are actually unloading the previous versions of your namespaces, and then loading them completely from scratch. If you switch chapter2.core to use defonce instead of def, you see the following:

lein repl
...
chapter2.core=> tasks
#object[clojure.lang.Atom 0x13312d27 {:status :ready, :val {}}]
chapter2.core=> (add-task "chores" "Mow the lawn.")
{"chores"
  {1 #chapter2.core.Task{:task "Mow the lawn.",
                      :creation-time #inst "2016-01-20T07:06:10.705-00:00"}}}
chapter2.core=> (add-task "chores" "Take out the trash.")
{"chores"
  {1 #chapter2.core.Task{:task "Mow the lawn.",
                      :creation-time #inst "2016-01-20T07:06:10.705-00:00"},
   2 #chapter2.core.Task{:task "Take out the trash.",
                      :creation-time #inst "2016-01-20T07:06:22.358-00:00"}}}
chapter2.core=> (require 'chapter2.core :reload)
nil
chapter2.core=> tasks
#object[clojure.lang.Atom 0x13312d27
{:status :ready,
 :val
   {"chores"
     {1 #chapter2.core.Task{:task "Mow the lawn.",
                        :creation-time #inst "2016-01-20T07:06:10.705-00:00"},
      2 #chapter2.core.Task{:task "Take out the trash.",
                         :creation-time #inst "2016-01-20T07:06:22.358-00:00"}}}}]
chapter2.core=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
chapter2.core=> (refresh)
:reloading (chapter2.core chapter2.handler chapter2.core-test)
:ok
chapter2.core=> tasks
#object[clojure.lang.Atom 0x27eab9ff {:status :ready, :val {}}]
chapter2.core=>

When you reloaded your namespace with require you avoided wiping out tasks, but reloading with tools.namespace again wiped it out. Really your only foolproof approach to writing completely reloadable code is to avoid global state altogether. The homepage for tools.namespace even describes some approaches to avoiding global state (https://github.com/clojure/tools.namespace#reloading-code-preparing-your-application). Luckily there is also a library called component (https://github.com/stuartsierra/component) that incorporates some of those concepts and provides a framework for structuring your application to avoid global state. We won't dive into the intricacies of components in this book; however, it is well worth investigating to help structure your code to avoid global state. Besides just the problems global state introduces for code reloading, it can also make your applications harder to understand.

Using a library like component and writing your application to avoid global state completely will require some additional effort and discipline in your development. You may decide in some of your applications that the extra cost isn't worth it, which may be valid. However, getting into the habit of avoiding global state while developing will eventually become easier and pay off over time.

SUMMARY

Getting feedback rapidly while developing is one of the most important factors in developer productivity. Clojure presents distinct advantages in this area, both in the design of the language and in the tooling in the Clojure ecosystem. In this chapter we dove into some basics for getting feedback while developing Clojure, some ways to leverage tooling in the Clojure ecosystem for rapid feedback, and some ways to approach structuring your code to make getting feedback easier. All of these will help make you a more productive Clojure developer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset