Functional Bytes Clojure, Scala en Java specialist

Mount-lite 2.1: inferred state graphs are back!

Jan 6, 2018 • Arnout Roemers

Version 2.0.0 of mount-lite was a breaking version. Most notably, as written in this previous blog post, version 2 is vastly smaller and simpler than version 1, and some features were removed.

As some of you may know, version 1 had the ability to have mount-lite infer the state dependency graph, instead of just a dependency sequence based on the load order. The tools.namespace library was used for this feature. More specifically, version 0.3.0-alpha-3 was used, expecting it to be released soon. That was two years ago, and it is still in alpha. This is not to nag on the developers of tools.namespace by the way.

Many other projects used and still use the stable version 0.2.11 of tools.namespace, yielding conflicts with mount-lite. Because of this and not seeing the feature being used a lot in the projects where I introduced mount-lite, the dependency graph feature was removed in the version 2 rewrite. The tiny core of version 2 went back to using the simpler load-order sequence, just as the original mount library does.

And then this issue was posted on the GitHub repository.

Dmitrii Balakhonskii wanted to use mount-lite, but required this inferred dependency graph feature. I could sympathize with him; it was a nice feature! And although the core of mount-lite has become very small, it offers an easy extension point. It also ships with a few extensions, but those were not sufficient for Dmitrii’s cyrus project template.

He gave a good pointer however on how to calculate the dependency graph with the stable version of the tools.namespace library. I thought, why not add this dependency graph feature again, but this time as an optional extension? So we did.

What does it do

Say you have the following namespaces with defstates:

;; file a.clj
(ns a)

(defstate a :start 1)

;; file b.clj
(ns b (:require a))

(defstate b :start (inc @a/a))

;; file c.clj
(ns c (:require a))

(defstate c :start (inc @a/a/))

(defstate c2 :start (inc @c))

;; file d.clj
(ns d (:require b c))

(defstate d :start (+ @b/b @c/c))

When Clojure loads these namespaces (assuming namespace d is the entrypoint), mount-lite will by default use the following states sequence:

a <--- b <--- c <--- c2 <--- d

If you would call the standard (mount/start #'c/c), it would start all states up to c in the sequence. This includes state b, which is technically not required, as the c state does not really depend on it.

This is where the new extension can help. Using this extension, a dependency graph is used instead. Schematically, the dependencies can be represented as below. Note that the dependencies are deduced based on namespace level; while state d does not technically require c2, it is deemed as such.

     +------- b <-------+
     |                  |
a <--+                  +--- d
     |                  |
     +--- c <--- c2 <---+

Using above dependency graph, mount-lite deduces that state b is not required by c, and therefore won’t start it. The same goes for when you stop your states up to a certain state; if all states are started, and you call (stop #'b/b), it would not stop state c or c2 (and neither state a of course).

Using it

For the extension to work, you need to include [org.clojure/tools.namespace "0.2.11"] in your project’s dependencies. Since the feature is an optional extension, this dependency is not included by default. And of course, you need [functionalbytes/mount-lite "2.1.1"].

The extension resides in the mount.extensions.namespace-deps namespace, and has a start and stop function, just as you are used to. So in most cases you can just replace mount.lite with the new namespace in your source code, and you’re done:

(ns core
  (:require a b c d
            [mount.extensions.namespace-deps :as mount]))

(mount/start #'b/b)

More documentation on the new extension can be found here.

As always, have fun!

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


06 267 145 02

KvK: 59562722

Algemene voorwaarden