No Ceremony

DI framework makes sense for OOP


In Java (or most OOP languages):

  • Objects need to be created
  • In most of the cases they are stateful
  • Dependencies (state) often need to be injected
  • Order of the creation needs to be determined/given for the injection to work

Hence an IoC framework such as Spring makes perfect sense (in Java):

for example creating a dataSource, a sessionFactory and a txManager in Spring

DI framework “hurts functionally”


In Clojure (or similar functional languages):

  • Explicit objects with state and behavior are discouraged
  • Code organized in namespaces and small functions
  • Functions are directly referenced across modules/namespaces

DI/IoC framework would hurt all of the above: “beans” with functionality can only be accessed via creating other framework managed “beans”: very much like a need to create an Object to access another Object’s stateful functionality.

Business


Let’s say we need to find a user in a database.

we would need to connect to a database:

;; in reality would return a database connection instance
(defn connect-to-database [{:keys [connection-uri]}]
  {:connected-to connection-uri})

and find a user by passing a database connection instance and a username:

;; pretending to execute a query
 
(defn find-user [database username]
  (if (:connection database)
    (do
      (println "running query:"
               "SELECT * FROM users WHERE username = "
               username "on" database)
      :jimi)
    (throw (RuntimeException. (str "can't execute the query => database is disconnected: " database)))))

examples are immediately REPL’able, hence we pretend to connect to a database, and pretend to execute the query, but the format and ideas remain.

Application Context


One way to use a stateful external resource(s) such as a database in the find-user function above, is to follow the Spring approach and to define an almost identical to Spring Lifecycle interface:

(defprotocol Lifecycle
  (start [this] "Start this component.")
  (stop [this] "Stop this component."))

Then define several records that would implement that interface.

By the way, Clojure records are usually used with methods (protocol implementations) that makes them “two fold”: they complect data with behavior, very much like Objects do. (Here is an interesting discussion about it)

(defrecord Config [path]
  Lifecycle
  (start [component]
    (let [conf path] ;; would fetch/edn-read config from the "path", here just taking it as conf for the sake of an example
      (assoc component :config conf)))
  (stop [component]
    (assoc component :config nil)))
(defrecord Database [config]
  Lifecycle
  (start [component]
    (let [conn (connect-to-database config)]
      (assoc component :connection conn)))
  (stop [component]
    (assoc component :connection nil)))
(defrecord YetAnotherComponent [database]
  Lifecycle
  (start [this]
    (assoc this :admin (find-user database "admin")))
  (stop [this]
    this))

Now as the classes (records above) are defined, we can create an “application context”:

(def config (-> (Config. {:connection-uri "postgresql://localhost:5432/clojure-spring"})
                start))
 
(def db (-> (Database. config) start))
 
(def yet-another-bean (-> (YetAnotherComponent. db) start))
;; >> running query: SELECT * FROM users WHERE username =  admin on #boot.user.Database{:config {:connection-uri postgresql://localhost:5432/clojure-spring}, :connection {:connected-to postgresql://localhost:5432/clojure-spring}}

and finally we get to the good stuff (the reason we did all this):

(:admin yet-another-bean)
;; >> :jimi

a couple of things to notice:

* Well defined order *

Start/stop order needs to be defined for all “beans”, because if it isn’t:

(def db (-> (Database. config)))
(def yet-another-bean (-> (YetAnotherComponent. db) start))
;; >> java.lang.RuntimeException: 
;;      can't execute the query => database is disconnected: boot.user.Database@399337a0
* Reality is not that simple *

All the “components” above can’t be just created as defs in reality, since they are unmanaged, hence something is needed where all these components:

  • are defined
  • created
  • injected into each other in the right order
  • and then destroyed properly and orderly

Library vs. Framework


This can be done as a library that plugs in each component into the application on demand / incrementally. Which would retain the way the code is navigated, organized and understood, and would allow the code to be retrofitted when new components are added and removed, etc. + all the usual “library benefits”.

OR

It can be done as a framework where all the components live and managed. This framework approach is what Spring does in Java / Groovy, which in fact works great in Java / Groovy.

.. but not in Clojure.

Here is why: you can’t really do (:admin yet-another-bean) from any function, since this function needs:

: access to yet-another-bean
: that needs access to the Database
: that needs access to the Config
: etc..

Which means that only “something” that has access to yet-another-bean needs to pass it to that function. That “something” is.. well a “bean” that is a part of the framework. Oh.. and that function becomes a method.

Which means the echo system is now complected: this framework changes the way you navigate, :require and reason about the code.

It changes the way functions are created in one namespace, :required and simply used in another, since now you need to let the framework know about every function that takes in / has to work with a “component”.

This is exactly what frameworks mean
When they talk about requiring a “full app buy in”
And while it works great for Java and Spring
In Clojure you don’t create a bean after bean
You create a function and you’re “keeping it clean”

“Just doing” it


In the library approach (in this case mount) you can just do it with no ceremony and / or changing or losing the benefits of the Clojure echo system: namespaces and vars are beautiful things:

(require '[mount.core :as mount :refer [defstate]])
(defstate config :start {:connection-uri "postgresql://localhost:5432/clojure-spring"})
 
(defstate db :start {:connection (connect-to-database config)})
;; #'boot.user/db
(mount/start #'boot.user/db)
;; {:started ["#'boot.user/db"]}
(find-user db "admin")
;; running query: SELECT * FROM users WHERE username =  admin on
;; {:connection {:connected-to postgresql://localhost:5432/clojure-spring}}
 
;; :jimi

done.

no ceremony.

in fact the db state would most likely look like:

(defstate db :start (connect-to-database config)
             :stop (disconnect db))

Managing Objects


While most of the time it is unnecessary, we can use records from the above example with this library approach as well:

boot.user=> (defstate db :start (-> (Database. config) start)
                         :stop (stop db))
#'boot.user/db
 
boot.user=> (defstate config :start (-> (Config. {:connection-uri "postgresql://localhost:5432/clojure-spring"}) start)
                             :stop (stop config))
#'boot.user/config

and they become intelligently startable:

boot.user=> (mount/start)
{:started ["#'boot.user/config" "#'boot.user/db"]}
 
boot.user=> (find-user db "admin")
;; running query: SELECT * FROM users WHERE username =  admin on
;; #boot.user.Database{:config #boot.user.Config{:path {:connection-uri postgresql://localhost:5432/clojure-spring},
;; :config {:connection-uri postgresql://localhost:5432/clojure-spring}},
;; :connection {:connected-to nil}}
 
;; :jimi

and intelligently stoppable:

boot.user=> (mount/stop)
{:stopped ["#'boot.user/db" "#'boot.user/config"]}
 
boot.user=> (find-user db "admin")
 
;; java.lang.RuntimeException: can't execute the query => database is disconnected:
;;   '#'boot.user/db' is not started (to start all the states call mount/start)

Easy vs. Simple


While usually a great argument, this is not it.

In this case this is pragmatic vs. dogma