The Component Pattern for System Construction

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:

  • database pools
  • HTTP servers
  • schedulers
  • caches
  • message consumers

Those things need to start, stop, and depend on one another in a predictable order. The pattern gives that process a shape.

The Pattern Is Bigger Than One Library

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:

  • stateful resources are explicit
  • dependencies are explicit
  • lifecycle is explicit
  • the running system is inspectable

That makes the pattern valuable even if a team later prefers a different system library.

Components Hold Runtime State, Not All Logic

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:

  • a datasource
  • a web server
  • a queue consumer
  • a scheduler
  • a cache with lifecycle

Bad candidates:

  • every pure transformation
  • every tiny helper namespace
  • business rules that do not own runtime resources

Lifecycle Matters More Than Construction

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.

Dependency Injection Should Stay Explicit

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:

  • startup order becomes deterministic
  • tests can substitute fake components
  • the running system becomes inspectable from the REPL

Why This Works So Well with the REPL

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 Pattern Has Real Costs

The Component README is unusually honest about the downsides:

  • it can be awkward to retrofit into codebases built around globals or singletons
  • small applications may not need the ceremony
  • the system map can become large and repetitive
  • all dependency relationships must be declared explicitly

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.

Common Mistakes

  • turning every piece of logic into a component
  • passing runtime dependencies through constructors instead of system wiring
  • hiding impure resource management inside ordinary helper functions
  • using the pattern in tiny apps where manual startup is simpler
  • assuming the library matters more than the architectural idea

Key Takeaways

  • The Component pattern is for explicit lifecycle and dependency management of runtime state.
  • The pattern is broader than the component library, though that library remains a canonical implementation.
  • Components should hold stateful resources, not every function in the program.
  • Explicit wiring makes systems easier to start, test, inspect, and restart.
  • The pattern pays off when lifecycle complexity is real, not when it is invented.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026