Lifecycle Composition

I’ve been thinking about how to build up software systems out of
stateful components. In past presentations and blog posts I’ve alluded
to a standard interface I use for starting and stopping stateful
components:

(defprotocol Lifecycle
  (start [this] "Begins operation of this component.")
  (stop [this] "Ceases operation of this component."))

Most of my Clojure programs follow the same basic structure: Each
subsystem or service is represented by a record which implements this
protocol. At the top, there is a “system” record which contains all
the other components.

I’ve gone through several versions of this protocol with the same
function names but slightly different semantics.

Side Effects, Mutable State

In the first version, start and stop were side-effecting
procedures which returned a future or promise:

(defprotocol Lifecycle
  (start [this]
    "Begins operation of this component. Asynchronous, returns a
  promise on which the caller can block to wait until the component is
  started.")
  (stop [this]
    "Ceases operation of this component. Asynchronous, returns a
  promise on which the caller can block to wait until the component is
  stopped."))

The calling code could dereference the future to block until the
service had successfully started. For example, a database-access
component might look like this:

(defrecord Database [uri connection-atom]
  Lifecycle
  (start [_]
    (future (reset! connection-atom (connect uri))))
  (stop [_]
    (.close @connection-atom)
    (future (reset! connection-atom nil))))

(defn database [uri]
  (->Database uri (atom nil)))

My idea was that multiple services could be started in parallel if
they didn’t depend on one another, but in practice I always ended up
blocking on every call to start:

(defrecord System [database scheduler web]
  Lifecycle
  (start [_]
    (future
      @(start database)
      @(start scheduler)
      @(start web)))
  (stop [_]
    (future
      @(stop web)
      @(stop scheduler)
      @(stop database))))

(defn system [database-uri]
  (let [database (database database-uri)
        scheduler (scheduler)
        web (web-server database)]
    (->System database scheduler web)))

Second Attempt

I decided to drop the requirement to return a promise from start/stop,
which meant that those functions became synchronous and had no return
value:

(defprotocol Lifecycle
  (start [this]
    "Begins operation of this component. Synchronous, does not return
  until the component is started.")
  (stop [this]
    "Ceases operation of this component. Synchronous, does not return
  until the component is stopped."))

This simplified the code calling start/stop, because I didn’t have to
worry about dereferencing any futures.

(defrecord System [database scheduler web]
  Lifecycle
  (start [_]
    (start database)
    (start scheduler)
    (start web))
  (stop [_]
    (stop web)
    (stop scheduler)
    (stop database)))

This also made it very clear that I was using start/stop only for
side-effects, forcing all of my components to contain mutable state.

Also, I had to manually place the calls to start/stop in the correct
order, to ensure that components were not started before other
components which depended on them.

Immutable Values

I decided to try to make the component objects more like immutable
values by redefining start/stop to return updated versions of the
components:

(defprotocol Lifecycle
  (start [this]
    "Begins operation of this component. Synchronous, does not return
  until the component is started. Returns an updated version of this
  component.")
  (stop [this]
    "Ceases operation of this component. Synchronous, does not return
  until the component is stopped. Returns an updated version of this
  component."))

In this version, start and stop feel more like functions. They are
still not pure functions, because they will have to execute
side-effects such as connecting to a database or opening a web server
port, but at least they return something meaningful.

This version has the added benefit of removing some mutable state, at
the cost of making the start/stop implementations slightly more
complicated. Now these functions have to return a new instance of the
component record:

(defrecord Database [uri connection]
  Lifecycle
  (start [this]
    (assoc this :connection (connect uri)))
  (stop [this]
    (.close connection)
    (assoc this :connection nil)))

(defn database [uri]
  (->Database uri nil))

One interesting feature of this pattern is that the system record can
reduce over its own keys to start/stop all the components:

(defrecord System [database scheduler web]
  Lifecycle
  (start [this]
    (reduce (fn [system key]
              (update-in system [key] start))
            this
            ;; Keys are returned in the order they were declared.
            (keys this)))
  (stop [this]
    (reduce (fn [system key]
              (update-in system [key] stop))
            this
            ;; Reverse the order to stop.
            (reverse (keys this)))))

However, this relies on implementation behavior of Clojure records:
the keys function will return the keys in the order they are
declared in the record. I’m reluctant to rely on that undocumented
behavior, so instead I’ll declare the ordering explicitly:

(def component-order
  [:database :scheduler :web])

(defrecord System [database scheduler web]
  Lifecycle
  (start [this]
    (reduce (fn [system key]
              (update-in system [key] start))
            this
            component-order))
  (stop [this]
    (reduce (fn [system key]
              (update-in system [key] stop))
            this
            ;; Reverse the order to stop.
            (reverse component-order))))

Dependency Order

I still don’t have good solution to specifying the order in which
components must be started/stopped. I’ve tried building a graph of
dependencies and computing the correct order. This would be similar to
what tools.namespace does with namespaces. But I haven’t been able to
come up with a good syntax for representing these relationships that
isn’t more cumbersome than just declaring them in order.

I’ve also tried using Prismatic’s Graph library or my own Flow library
to define the graph of dependency relationships, but neither of those
libraries can produce a structure that remembers the graph after it
has computed its output, so I have no way to recover the dependency
relationships after constructing the system object.

Dependency Injection and State

This technique is a form of dependency injection through constructors.
The choice of whether to make the individual components mutable,
stateful objects has an impact on how I can use them later on. In the
original version of this pattern using mutable objects, each component
gets stable references to other components it depends on. In the later
version using immutable data structures, each component gets
references to the constructed versions of other components it
depends on, but not the started versions of those components,
i.e. the values returned from start.

So far, this has not been a problem in the programs I write. For
example, a Datomic database connection is always recoverable from the
URI, so I don’t need to store it explicitly. But other components,
particularly components which rely on external state to function
properly, might need to be mutable so that their dependents can still
use them via the references the received in their constructors. I
could still have start and stop return new values, but they would
also have to modify some mutable state (such as a Ref or Atom) along
the way. As always, mutable objects muddy the distinction between
values and identities.

I’ve also experimented with variations of start and stop that
pass in other “started” components, but this was cumbersome and hard
to generalize through a single interface.

So I don’t have a perfect system. It works well enough for the
applications I’ve developed with it so far, and it facilitates my
REPL-driven development workflow, but I always have to adapt to
circumstance. Eliminating mutable state is generally a good thing,
but it can also be limiting, especially when you have to deal with
external state.

7 thoughts on “Lifecycle Composition”

  1. Could you explain more about the problems you’ve faced in using a graph-like library for dependency order? I’ve been using this approach for a bit over a year now with good results, but we may be thinking about different things.

    I’ve been using a library like Prismatic’s graph (and Prismatic’s version itself in later projects) which uses the topological sort of the graph as the order in which to start the components. Once the system graph is computed the topo sort is stored as metadata onto the system object (map) which is then used in #’stop to guarantee components are stopped in reverse order. The graph library doesn’t need to remember the order, the Lifecycle #’start is responsible for storing it in the metadata for later use.

  2. I can’t help but think about how this resembles Erlang and it’s OTP. It looks like every sufficiently well designed system contains half of OTP. I believe that you can benefit a lot from investing some time in it, besides, it’s very fun by itself.

  3. Stuart, thanks for the series, I have been enjoying it. Is it possible for you to release a sample project ( a sort of hello world or little more than it), that utilizes the above ideas. It will be cool to see them in action.

  4. Stuart, can you give an example where a mutable reference would be preferred instead of an immutable value. In which case the above system would not work ?

    I am also using a complete clojure stack (including datomic), so the immutable values concept should owrk. However want to understand where the above scheme of things will break.

  5. We found it beneficial to make Lifecycle#start:

    (start [this started]
    (reduce (fn [system key]
    (update-in system [key] start system))
    this component-order))

    Which allows to depend on already started components. Do you see any problems with that?

Comments are closed.