Managed Component System
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 System Lifecycle Management 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 Description can then be started as a whole, which will start all components in topological order.
Various extensions and variations exist, but these are the key ingredients.
Relationship to Dependency Injection
While system lifecycle management in Clojure shares similarities with, and can be said to be a type of, dependency injection, it serves a more specialized purpose and boasts 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, or facilitate dynamic reconfiguration.
Alessandra Sierra's Component library and Reloaded Workflow Article
The cultural notion in the Clojure ecosystem of a "component" library and its purpose originated with Alessandra Sierra's Component library, and the accompanying Clojure Workflow Reloaded article.
It highlighted the main purpose of system lifecycle management: 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 Alexis, editing by Arne Brasseur)