Reloadable Clojure

A quick demo of application of the Component library for Clojure. This approach to managing your application state enables:

  • Rapid feedback cycles that accelerate learning
  • Parallel test execution with little additional work
  • A composeable, inspectable representation of state

Creating a component

We’ll create a couple of example components to play with. Each will only make use of clean, in-memory state that approximates reality. I want four components with a simple dependency graph.

Sorry, your browser does not support SVG.

We’ll end up with an API component that requires a cache, DB, and queue.

Cache

(defprotocol ICache
  (get-value [this key])
  (put-value [this key value]))

(defrecord Cache [client]
  component/Lifecycle
  (start [c]
    (assoc c :client (constantly ::ok)))
  (stop [c]
    (dissoc c :client))

  ICache
  (get-value [_ key]
    (get client key))
  (put-value [_ key value]
    (assoc client key value)))

Database

(defprotocol IQuery
  (execute [this statement context]))

(defrecord DB [pool]
  component/Lifecycle
  (start [c]
    (assoc c :pool (constantly [])))
  (stop [c]
    (dissoc c :pool))

  IQuery
  (execute [_ statement context]
    ;; This would delve into the IO realm where things are probabilistic and
    ;; open to interpretation. Spend too long here and you may never return.
    []))

Queue

(defprotocol IQueue
  (enqueue [this msg]))

(defrecord Queue [broker]
  component/Lifecycle
  (start [c]
    (assoc c :broker (constantly {})))
  (stop [c]
    (dissoc c :broker))

  IQueue
  (enqueue [_ msg]
    :ok))

API

(defn make-handler
  [api]
  (comment
    ;; All of the following will be available here.
    (enqueue (:queue api) {:hi "there!"})
    (execute (:db api) "SELECT 1 as one;" {})
    (get-value (:cache api) "W/abc123abc123abc123"))

  ;; Everything's OK!
  (constantly {:status 200 :body "OK\n"}))

(defrecord API [cache db handler queue]
  component/Lifecycle
  (start [c]
    (assoc c :handler (make-handler c)))
  (stop [c]
    (dissoc c :handler)))

System

(defn make-system
  [config]
  (component/system-using
   (component/system-map
    :api (map->API (:api config))
    :cache (map->Cache (:cache config))
    :db (map->DB (:db config))
    :queue (map->Queue (:queue config)))
   {:api [:cache :db :queue]}))