Functional Bytes Clojure, Scala en Java specialist

mount-lite 2.0

Dec 10, 2016 • Arnout Roemers

In a previous blog post the mount-lite library was introduced. In short, it explained why I like the premise of mount, and what moved me to create a spin-off. This time I want to talk about what has changed between the 0.9.x versions of that blog post and the recently released 2.0.0 version.

A little history

With the 0.9.x versions, the mount-lite library used to be bigger. It was different from the original mount, with features such as a composable data-driven API, bindings, parallel starting and stopping, starting and stopping “up to” a particular state, automatic dependency graphs, and cleaner substitutions. In the mean time, the original mount has dropped unnecessary features, has also created a composable API, and is improving substitutions as well. It is good to see this, as mount is more popular, and all kudos should still go to the original author of course.

The reason for a new version

The 2.0.0 version introduces the ability to run multiple systems of states simultaneously. In other words, a single defstate can be started multiple times in different contexts. One major reason for introducing this feature, is to be able to run test suites in parallel.

As this is a breaking change, it was a good opportunity to get rid of the excess, based on experience in several larger projects. With the new version, mount-lite still has it’s own take and feature set with respect to the original mount. In short, apart from the multiple-instances feature, the following has changed:

  • All the states need to be dereferenced. That means, using @ or deref when using a started state. This enables the multiple-instances feature.
  • The bindings feature and the parallel start/stop feature have been removed, as those were rarely used and only added complexity.
  • The only and except features have been removed as well, as the up-to feature is all you (should) need. The unique up-to feature is now standard. The only and except features could be harmful - they could leave your system in a bad state - and they rarely used. They could get your system in a bad state.
  • The cleaner substitutions are here to stay though, and have improved slightly, by having the ability to declare substitutions around the start function. This means that you don’t have to change anything within the (start) expression or its immediate vicinity.
  • States are not stopped automatically anymore when you reload them. Users found this and its options difficult to understand. Now they can take matters in their own hand, using standard clojure.

Because of these feature changes, the API has been trimmed down to a simple mininium, not requiring it to be composable or data-driven.

The new minimal API

In my experience, most mount-lite users were not the power user I expected them to be. So many options the data-driven composabe API offered, were rarely used. The new API comprises of only the bare minimum of features that are actually used. The API has become simpler because of this, and therefore easier to learn and understand.

The API now comes down to:

  • Defining states using (defstate ...), only having a :start and :stop expression.
  • Starting and stopping all states using (start) and (stop), or starting and stopping up to a certain state var using (start #'my-state) or (stop #'my-state).
  • Checking the status of all the states using (status).
  • Declaring substitutes by using the (with-substitutes [#'my-state (state ...)] ...) macro, which can be nested.
  • Creating multiple systems of states by using the (with-session ...) macro.

That’s it.

If you can’t live without the removed features, know that you can add some of them yourself by using the extension point mount-lite now offers. Otherwise, keep using the 0.9.x version, as it will still be supported.

Multiple systems of states

But how do you use that new feature, having multiple systems of states simultateously living next to each other? The mount-lite library does this in a transparent way, i.e. there is no difference in how the library is used, whether this feature is utilized or not. This is in contrast with the yurt library from the original mount.

When you just (start) your states, you start them in the default context. The entire application accesses those states as normal. By wrapping your (start) with the with-session macro, the thread that is then spawned - and all of its subthreads - will use a new system of states. The states themselves and the code that uses them does not change a bit. The states in that session are automatically stopped when the thread is done.

The example below shows how we use the with-session macro to spin up a new thread and execute its body. The example starts up two systems of states this way, and the use of CountDownLatches ensures the systems run concurrently when printing the value of the foo state.

;; Define a simple state
(defstate foo
  :start 1
  :stop (println "stopping"))

;; Define latches for synchronizing threads
(def started (CountDownLatch. 2))
(def stopping (CountDownLatch. 2))

;; Start the states in the "root" session.
(start)

;; Spawn a new session thread.
(with-session
  ;; Use substitutes to alter the standard defstate behaviour
  (with-substitutes [#'foo (state :start 2 :stop (println "sub stopping"))]
    ;; Start the states in this new session.
    (start))
  ;; Set this thread as started and wait for the other to have started.
  (doto started .countDown .await)
  ;; Spawn a subthread that prints the value of foo, to demonstrate
  ;; subthreads have access to the session just as well.
  (thread
    (println @foo)
    ;; Ready to stop
    (.countDown stopping))
  ;; Wait for the root session to be ready to stop as well.
  (.await stopping)
  ;; This session stops automatically.
  )

;; Set this thread to started and wait for the other to have started.
(doto started .countDown .await)
;; Print the value of foo in this context.
(println @foo)
;; Signal to stop and wait for the other to signal the same.
(doto stopping .countDown .await)

;; Stop the root session.
(stop)

The printed output is shown below. As you can see, two different values are printed, and subthreads use running states from parent threads. The order in which 1 and 2 are printed, or the order in which stopping and sub stopping are printed, might of course be swapped.

1
2
stopping
sub stopping

There you have it

The mount-lite library now truly fulfills the “lite” part. With version 2.0.0 it has gone back to the core of its functionality, based on real world projects and user experience. I hope you like it. The documentation has been updated to reflect this new version, including a migration guide. If you have questions or suggestions, I’d love to hear them, for example on twitter or the #mount channel on Clojurians Slack.

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

Boekelo

06 267 145 02

KvK: 59562722

Algemene voorwaarden