Learn how the Component pattern manages lifecycle and dependencies for stateful Clojure systems, and when the pattern helps more than it hurts.
Component pattern: A way to organize an application as a set of stateful parts with explicit lifecycle and dependency relationships, instead of letting runtime resources appear implicitly across the codebase.
The Component pattern matters in Clojure because pure functions and plain data are not the whole story. Real systems still have:
Those things need to start, stop, and depend on one another in a predictable order. The pattern gives that process a shape.
Stuart Sierra’s component library is the classic implementation of this idea, and its current README still describes it as a tiny framework for managing lifecycle and dependencies of software components with runtime state. It also documents 1.2.0 as the latest stable release.
But the important part is the pattern itself:
That makes the pattern valuable even if a team later prefers a different system library.
One subtle but important rule from the library README is that most functions are just functions, and most data are just data. Components exist to manage stateful resources, not to swallow the whole codebase.
Good candidates for components:
Bad candidates:
The heart of the pattern is not object construction. It is controlled start/stop behavior.
1(defrecord Database [config connection]
2 component/Lifecycle
3 (start [this]
4 (if connection
5 this
6 (assoc this :connection (connect! config))))
7 (stop [this]
8 (when connection
9 (.close connection))
10 (assoc this :connection nil)))
This style makes runtime transitions visible. It also encourages idempotent lifecycle methods, which the Component README explicitly calls out as useful for safer cleanup and restart behavior.
The Component approach expects runtime dependencies to be injected by the system, not hidden in globals or pushed through constructors prematurely.
1(component/system-map
2 :db (new-database config)
3 :app (component/using
4 (new-app config)
5 [:db]))
That explicit wiring helps at least three things:
One of the strongest benefits, and one the README emphasizes, is that the system map keeps state reachable and restartable during development.
flowchart TD
A["Build system map"] --> B["Inject dependencies"]
B --> C["Start components in dependency order"]
C --> D["Inspect or exercise the running system at the REPL"]
D --> E["Stop components in reverse order"]
This is a natural fit for Clojure because the REPL becomes much more useful when a whole running system can be started, inspected, swapped, and stopped without full process churn.
The Component README is unusually honest about the downsides:
Those are not minor caveats. This pattern helps most when lifecycle complexity is already real. If the app is tiny, the pattern can become architecture theater.
component library, though that library remains a canonical implementation.