Introduction

A system consists of components working together. These components need to be initialized (started) and wired together, and when the system is torn down the individual components need to be torn down (stopped). The order in which they are started and stopped depends on the dependencies between the components.

Components are often stateful, leading to specific concerns when doing Interactive Programming against a live process. One needs to resolve issues arising from stale state, and allow for dynamic reconfiguration and reloading when in the course of development the system structure or configuration changes.

Pattern Description

In a Reloadable Component Systems there are Components, which have start and stop routines. The start routine accepts configuration and dependent components, the stop routine accepts the previously started component. These components are assembled and provided configuration values in a System Description.

The System can then be started as a whole, which will start all components in topological order, and stopped again, which does a clean shutdown in reverse topological order.

A refresh or reset consists of doing a full code unload/reload in between stopping the system and restarting it, with the system now freshly booting from the newly loaded code.

Relationship to Dependency Injection

While Component Systems in Clojure share similarities with, and can be said to be a type of, dependency injection, they serves a more specialized purpose and boast a unique history within the Clojure ecosystem.

Much of this has to do with the dynamic nature of Clojure, and the cultural preference and excellent affordances for Interactive Programming (i.e. using a REPL). This means a system doesn't just need to be able to start up once, it also needs to be torn down cleanly, restart, receive new configuration while it's running, and start or stop incrementally (intentionally or because of failures of certain components to start). Different solutions in the Clojure space have differing degrees of support for these dynamic use cases.

Frameworks like Java Spring use XML configuration to declare system and component structure and dependencies, by providing class names and construction hints. This is a highly static approach, which doesn't provide support for full lifecycle management, code reloading, or facilitate dynamic reconfiguration.

Alessandra Sierra's Component library and Reloaded Workflow Article

This pattern and its particular purpose in the context of Clojure was first described by Alessandra Sierra's, who provided an initial implementation in the Component library, and published the accompanying Clojure Workflow Reloaded article.

It highlighted the main purpose of a Reloadable Component System: eliminating stale REPL state.

… some aspects of Clojure's runtime are not quite as late-binding as one might wish for interactive development. For example, the effect of a changed macro definition will not be seen until code which uses the macro has been recompiled. Changes to methods of a defrecord or deftype will not have any effect on existing instances of that type.

Part of the solution is found in tools.namespace, which allows reloading a set of namespaces in dependency order. However as Sierra points out tools.namespace alone is not enough. It is also imperative that the version of the application that's running, and any in-memory state it holds, matches with version of the application that is encoded in the source files.

So the proposed approach is to combine tools.namespace with a "component" (system lifecycle) library. It shuts down the system, removing any stale state, then reloads all code, and then uses the newly loaded code to construct a new system state.

History

2024-06-10

  • First public version (Ariel Alexi, editing by Arne Brasseur)