No Ceremony

DI framework makes sense for OOP

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.

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 :confignil)))

* 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:

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

This entry was posted on Monday, November 21st, 2016 at 19:27 and is filed under clojure, mount.
You can follow any comments to this entry through the RSS 2.0 feed.
Both comments and pings are currently closed.