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.
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]}))