The other problem that Component tackles is composing Lifecycle instances into systems that, conveniently, implement Lifecycle as well. Here is an example of how it’s done based on the library’s Readme:

Each component corresponds to a record implementing Lifecycle . On startup the Component will walk though them executing (start) after relevant dependencies — expressed with the using function — have been resolved.

The code above should look a bit familiar to Clojure developers, even those who have never had anything to do with Component. It is, after all, Component’s equivalent of the let macro! Like a let macro system-map let’s you compose expressions evaluating the first on the right [ (new-database...) ], ‘binding’ the result value to a name [ :database ] and making that value available to the expressions further down [ (component/using ... [:databse ... ]) ].

Unlike the let macro, system-map utilises keywords rather than symbols which is of significant consequence. Binding of symbols is native to the language and comes with well defined semantics and clear scope making it easy to use in arbitrary expression. Conversely, Component’s usage of keywords for binding is an ad-hoc construct with blurry semantics and expression limited to associating stuff to a record.

Let’s explore that last point a little. With Component, the only thing that can possibly have dependencies is a Lifecycle record, the only way to access its dependencies is a key lookup from within of the (start) method and, finally, the only way for component to expose state of its own is by assoc ing another key. If your component was a simple function of two other components you can’t just apply that function. You are going to need yet another component that will unwrap dependencies, apply the function in question and wrap the result again. This is in stark contrast with let macro where function application is first-class.

Implementation of example-component could look like this

Components without actual state of their own can be easily spotted by the (stop) method doing nothing but letting go of references (example from daniesz/system).

Composing resources

To bring the convenience of using let macro to resource management Clojure gives you with-open , a macro corresponding to Java’s try-with-resources. It enables you to declare as many resources as you need and rest assured they will all get released properly by the time the processing is done — even in the presence of exceptions. Note that this is not a guarantee offered by Component. It’s the perfect tool to compose resources management with your business logic, keeping the two concepts separate.

The adaptation of my-system example looks very similar to the original thing but this time the resources are bound to symbols giving you incomparable freedom of expression, limited only by the fact that with-open expects all expressions to return a Closeable . I was asserting earlier that you don’t need a lifecycle to manage state and that the only thing that needs managing is the resource, however, a resource can depend on state as much as state can depend on a resource. It’s often necessary to interleave the definition on both making the limitation painful. Suppose our scheduler depended on some stateful object, like cache, that is not a Closeable . To introduce this dependency we need an intermediate let .

Although the above may not look alarming yet, it can quickly get out of hand if the dependencies between state and resources are complex enough. The problem seems not to be isolated to this design. To compose instances of Lifecycle with arbitrary state, Component provides a bit dirty of a workaround, essentially, making everything implement that protocol.

This only partially fixes the problem for Component as arbitrary state won’t be able to depend on any resources. There is another reason why this solution is not too compelling in our case however. Closeable is an interface, not a protocol — you can’t implement an arbitrary interface to a class you don’t control. What you can do, instead, is to implement a wrapper that implements Closeable but also hold some state.