UP | HOME
dix

Software Curmudgeon

Mumblings and Grumblings About Software

An Actions Tale
Published on Aug 07, 2013 by dix.

I have been working recently on a web app written in Clojure. I wanted to learn both Clojure and Bridge better and this app has helped me in both regards. I want to tell you the story of an action in this app and how I implemented it. It is a story that demonstrates one of my great struggles while I write Clojure code. As I work in Clojure, I often feel myself writing Rails code with more parentheses. I had this very problem with the action that is the subject of our story. This action parses a request, builds up a new object representing the result of a hand of Bridge and associates it with an in-progress Chicago Bridge game. As, you can see the code is very procedural. First, get the data we need, check if everything is present, then update the storage, then respond with a redirect.

(defn add-hand-to-chicago-game [request]
  (let [id (get-in request [:params :id])
        game (get @chicago-games id)
        hand (get-in request [:params :hand])]
    (if (not game)
      (not-found "NOT FOUND"))
    (swap!
     chicago-games assoc id (conj game (process-hand-params hand)))
    (redirect (str "/chicago_games/" id))))

This really bothered me. This is not the beauty of functions operating on data that I was longing to see. It got even worse once I added some validations. This version of the action doesn’t even exist in the Git repo anymore so I can’t show you. I hated it so much.

Finally, I bit the bullet. I broke this action apart into functions upon functions upon functions. Ahhhh, here was the beauty I was looking for…sort of. Granted, it didn’t really look that beautiful. First, it’s giant. I took a seven line function and ballooned it out into almost 40 lines.

(defn extract-params [request]
  {:id (get-in
        request [:params :id]) :hand (get-in request [:params :hand])})

(defn enrich-with-prereqs [request]
  (assoc request :game (get @chicago-games (:id request))))

(defn validate-prereqs [prereqs]
  (assoc prereqs :errors (validate/hand (:hand prereqs))))

(defn derive-status [prereqs]
  (assoc prereqs
         :status
         (cond
           (nil? (:game prereqs)) 404
           (not (empty? (:errors prereqs))) 422
           :else 201)))

(defn persist [request]
  (let [new-game (conj (:game request)
                       (process-hand-params (:hand request)))]
    (swap! chicago-games assoc (:id request) new-game)
    (assoc request :game new-game)))

(defn conditionally-persist [request]
  (if (= (:status request) 201) (persist request) request))

(defn render [request]
  (cond (= (:status request) 404) (not-found "NOT FOUND")
        :else (views/display-chicago-game (:id request) (:game request)
                                          (:errors request))))

(defn add-hand-to-chicago-game [request]
  (-> request
      extract-params
      enrich-with-prereqs
      validate-prereqs
      derive-status
      conditionally-persist
      render))

I suspect anyone who wanted to get any real work done in this language would object to this style. And I would totally agree with them. However, for this toy app, it works fine.

In thinking about this, my mind is drawn towards thinking about Ring. Ring is a Clojure middleware stack and spec. It takes a similar approach to Ruby’s Rack framework. It works by applying a series of functions to the data that is the HTTP request and returning data that will be the HTTP response. In order to get the esthetic I want, perhaps I should be pushing more of the contents of this action into the Ring stack. Rather than extracting params in the action, perhaps a param-extractor middleware which is built up with a map of pairs of verbs and actions pointing to a map representing the parameters to be extracted. At this point, the contents of the action could be changed to only validation, persistence, and rendering. I may attempt spiking this out to see what it would look like. Until then, that’s the story of this action and I’m sticking to it.