Components!
Taming Clojure applications
David Dossot
Every application, ever*
● Subsystem lifecycle
● Long running processes
● Connections pools
● Caches
* almost
The Clojure Curse*
* http://www.winestockwebdesign.com/Essays/Lisp_Curse.html
● Clojure can do anything
● No official app framework
● NIH is a risk
● We went there...
Take #1 ~ Atoms ?
(def ^:dynamic server (atom nil))
(defn start [config]
(reset! server
(make-server config)))
(defn stop []
(.stop @server))
Take #1 ~ Atoms ✘
● Easy :)
● Shared state :(
● All over the place :(
● Reload unfriendly :(
● No dependencies :(
Take #2 ~ Maps ?
(defn start [execution-context config]
(assoc execution-context
:server (make-server config)))
(defn stop [execution-context]
(.stop (:server execution-context))
(dissoc execution-context :server))
Take #2 ~ Maps ✘
● Pure functions :)
● Reload friendly :)
● All in one place :)
● Ad-hoc lifecycle :(
● No dependencies :(
Take #3 ~ Components ?
(defrecord Server [server config]
component/Lifecycle
(start [this]
(if server ; already started
this
(assoc this :server (make-server config))))
(stop [this]
(if-not server ; already stopped
this
(do
(.stop server)
(assoc this :server nil)))))
Take #3 ~ Components ✔
● Pure functions :)
● Reload friendly :)
● All in one place :)
● Lifecycle API :)
● Dependencies :)
Caveat emptor
● Invasive
● All fns that reify the module's behaviour
●
Wish clj had Erlang's parameterized modules*
● Records can be weird
● lein clean is your friend
* experimental feature, now removed :(
Resources
https://github.com/stuartsierra/component
https://youtu.be/13cmHf_kt-Q

Taming Clojure applications with Components

  • 1.
  • 2.
    Every application, ever* ●Subsystem lifecycle ● Long running processes ● Connections pools ● Caches * almost
  • 3.
    The Clojure Curse* *http://www.winestockwebdesign.com/Essays/Lisp_Curse.html ● Clojure can do anything ● No official app framework ● NIH is a risk ● We went there...
  • 4.
    Take #1 ~Atoms ? (def ^:dynamic server (atom nil)) (defn start [config] (reset! server (make-server config))) (defn stop [] (.stop @server))
  • 5.
    Take #1 ~Atoms ✘ ● Easy :) ● Shared state :( ● All over the place :( ● Reload unfriendly :( ● No dependencies :(
  • 6.
    Take #2 ~Maps ? (defn start [execution-context config] (assoc execution-context :server (make-server config))) (defn stop [execution-context] (.stop (:server execution-context)) (dissoc execution-context :server))
  • 7.
    Take #2 ~Maps ✘ ● Pure functions :) ● Reload friendly :) ● All in one place :) ● Ad-hoc lifecycle :( ● No dependencies :(
  • 8.
    Take #3 ~Components ? (defrecord Server [server config] component/Lifecycle (start [this] (if server ; already started this (assoc this :server (make-server config)))) (stop [this] (if-not server ; already stopped this (do (.stop server) (assoc this :server nil)))))
  • 9.
    Take #3 ~Components ✔ ● Pure functions :) ● Reload friendly :) ● All in one place :) ● Lifecycle API :) ● Dependencies :)
  • 10.
    Caveat emptor ● Invasive ●All fns that reify the module's behaviour ● Wish clj had Erlang's parameterized modules* ● Records can be weird ● lein clean is your friend * experimental feature, now removed :(
  • 11.