Functional Bytes Clojure, Scala en Java specialist

New library: mount-lite

Feb 11, 2016 • Arnout Roemers

Just enough structure

As many of the Clojurists will know, the reloaded workflow has been a popular way of developing Clojure(Script) applications. A library that supports this for more than two years now is Component. While I have used Component successfully, I was happy to learn about a new kid on the block a few months ago, called mount.

There have always been proponents and opponents to Component - as it is a fairly opinionated library. And whether the way mount tackles the reloaded workflow is any improvement is being discussed as well. I for one like it very much. For me it delivers on what it set out to do: keeping Clojure fun. It feels more Clojuresque, but I find it has also the following important properties:

  • It is small (at least from a user’s perspective), less opinionated, and provides just enough structure (oddly enough) and - more importantly - nothing more.
  • It still allows for protocols and records, if that’s applicable.
  • It forces you to put state where it belongs - in the application namespaces, not in library namespaces.
  • You don’t need to pass the stateful parts around everywhere explicitly.
  • It is more flexible in (re)starting or stopping parts of the system.
  • It is REPL friendly.

I don’t want to say Component is bad, and I also don’t want to repeat the already great discussions on that subject. I am here to tell you that I like mount, why I created a spin-off from scratch called mount-lite, and why you might like it.

Why mount-lite was created

As I said, I discovered mount quite early. While checking it out, I saw it has some nice options on how to and which states to start or stop. I quickly noticed though that those options do not compose well. I tried to steer the project to have a somewhat better composable API, by starting a discussion in this mount issue early on, but the project had other priorities, which is perfectly fine of course.

After waiting to see what direction mount would go in, I decided it was best to develop mount-lite, if only to realize my own idea of a data driven and composable API. The library is very suitable to have a simple internal model of what to start/stop and how. Therefore, the internal start* function, simply takes the following map:

{#'state-var {:start ... :stop ...}
 ...}

It is simply a map of all the state vars that need to be started and their respective :start and :stop functions. Those functions might come directly from the state var, or from some substitution. The start* function does not care. It is the public start function that takes all kinds of options, and brings it down to the above map. This is in contrast to the original mount, where each option has its own function and logic. Mount-lite’s design makes it simple to compose the options and add new options, without disrupting the public API or most of the internals.

The options to start are specified via one or more option maps. These option maps can be generated by (combinator-like) builder functions. For instance, if one wants to start only the state #'foo and substitute its behaviour, one could call start with data directly:

(start {:only [#'foo]
        :substitute {#'foo {:start (constantly "bar")}}})

Or one could use the builder functions, for a more convenient REPL experience:

(start (only #'foo)
       (substitute #'foo (state :start "bar")))

One can also thread these builder functions together, to create an option map:

(def opts (-> (only #'foo)
              (substitute #'foo (state :start "bar"))))

(start opts)

Whatever the style you prefer, it can be done, because ultimately it is data driven.

Alright, experiment succeeded. I had a small mount like library, having a better API (in my opinion). But hey, now that I have my own library, I can add more ideas to the mix!

What makes mount-lite unique

No suspending

Right from the start, I did not implement the possibility to “suspend” and “resume” states. This has long been a feature of the original mount, but has also been removed there some days ago. The currently available start and stop options (for both mount and mount-lite) are enough.

Cleaner substitutions

Also right from the start, I had a different idea about how to handle “substitutions”, i.e. starting a state with different (mock) lifecycle functions. With mount, one would write (start-with {#'app/foo #'app.test/test-foo}), meaning that when #'app/foo is started, it should use the lifecycle functions of the state #'app.test/test-foo. The latter is also defined with defstate, meaning extra care must be taken that it is not started if not used as a substitute.

In mount-lite, substitutions are anonymous states, or at least not defined with defstate (they can be bound to a var of course). So, as we have seen already, in mount-lite one would write (start (substitute #'app/foo app.test/test-foo)), meaning it will use the value (a map) of app.test/test-foo for the lifecycle functions. This way, substitution are just that, not global states. It also means that the lifecycle map can be inline, such as (start (substitute #'app/foo {:start ...})). Simple, yet effective.

Dependency graph

Instead of building a sequence of ordered states, mount-lite builds a dependency graph of them. It does this by leveraging the tools.namespace library. By default, a state var depends on previously defined state vars in the same namespace and transitively required namespaces. This can be influenced however, for power users, by adding :dependencies to the meta data of the state var. This dependency graph provides the basis for the next three unique features.

Start and stop “up-to”

One of the start/stop options that can be supplied, is :up-to. The value of that option is a state var. It means that, when starting, all dependencies of that state are started first, and lastly the state itself is started. This way one can bring up only a part of the system, for instance when testing. Below is a figure of a simple dependency graph of four states. The arrows in the diagram mean “depends on”. In the figure, the white states have been started, after issuing the following:

(start (up-to #'b))

Mount start up to example

When supplying the :up-to option to stop, first all the dependents of the given state are stopped, and lastly the state itself is stopped. This safely brings down only the necessary part of the system, without any dependents suddenly missing a dependency. Again, the gray states in the figure below show which states have been stopped, after the following expressions:

(start)
(stop (up-to #'b))

Mount stop up to example

Cascading stop on redefinition, or not

Whenever you are hacking away in a REPL on your project, and redefine an existing defstate, the default behaviour is to stop that current state first. This will nicely clean up any resources first. The original mount does this as well. But in mount-lite, this stop is actually using the :up-to option we just discussed. We call this a “cascading” stop. Only the parts of your system that is being redefined is stopped cleanly, and the rest keeps running. Simply call start and the full system is back up in no time.

Of course, there are situations that it is not desired to automatically stop all the states up to the redefined state. Because of this, the reloading behaviour can be altered globally, by calling the on-reload function. More details on the various on-reload modes can be found in the documentation of mount-lite.

Parallel start and stop

Another great feature that is possible because of the internal dependency graph, is being able to start and stop independent states in parallel. Simply supply the number of threads one desires to use via the :parallel start/stop option, and an internal executor service will do the rest for you. This may significantly reduce the start and stop times of your application. For example, the red states in the figure below are started in parallel.

(start (parallel 2))

Mount parallel start example

If state b finishes starting before state c finishes, state a might be starting in parallel with c as well. The next figure shows which states are stopped in parallel.

(stop (parallel 2))

Mount parallel stop example

Again, if state a finishes stopping before state c finishes, state b might be stopping in parallel with c as well.

Of course, one does not have to use the parallel functionality. If the option is left out, the current thread and the simple sequential ordering is used to start and stop the states.

There you have it

In the end, mount-lite turned out not to be so “lite” after all. Yet, for me at least, these are nice features to have. I showed mount-lite to Anatoly (the author of the original mount) first. He liked the result, and thought mount and mount-lite can co-exist very well together. Thus, I open sourced it, for all of you who might enjoy it. Have questions or suggestions? I’d love to hear them, for example on twitter or #mount channel on Slack.


Clojure - Scala - Java - JavaEE - Datomic - Reagent - Figwheel - HugSQL - JavaScript - Node.js - Maven - SBT - XML - XSD - XSLT - JSON - jQuery - HTML - HTMX - React - Redux - OAuth - REST - GraphQL - ZooKeeper - Kafka - Akka HTTP - PostgreSQL - ElasticSearch - Cassandra - Redis - Mule - RabbitMQ - MQTT - SOAP - Linux - macOS - Git - Scrum - Emacs - Docker - Kubernetes - Ansible - Terraform - Jenkins - GitHub - GitLab - Devops - Raspberry Pi - Event Sourcing - Functional Reactive Programming - Ports and Adapters (Hexagonal)


Functional Bytes, 2013-2024

Boekelo

06 267 145 02

KvK: 59562722

Algemene voorwaarden