Learn how to model the Reactor pattern in Clojure with channels and dispatch loops, and how to keep the reactor non-blocking without confusing it with general-purpose background work.
Reactor pattern: A design in which a central dispatcher waits for events or readiness signals, routes them quickly, and keeps the hot path responsive by pushing slow work outward.
The Reactor pattern is easy to describe loosely and implement badly. It is not “any async code.” It is a design centered on fast dispatch. In Clojure, core.async can model the handoff points clearly, but only if the reactor loop stays narrow.
A reactor should own:
It should not own:
That distinction is the difference between a reactor and a bottleneck.
1(require '[clojure.core.async :as async])
2
3(defn start-reactor [in handlers]
4 (async/go-loop []
5 (when-some [event (async/<! in)]
6 (when-let [handle! (get handlers (:type event))]
7 (handle! event))
8 (recur))))
This loop is intentionally boring. That is a strength. It makes the real design questions visible:
These are not the same thing:
If the dispatch loop starts behaving like a worker pool, the reactor has lost its job.
Every reactor needs an overload strategy:
Without an explicit overload answer, the reactor becomes a place where latency quietly accumulates.
Reactor handlers should have clear rules:
If handlers are unconstrained, the reactor loop stays small in code but becomes operationally unpredictable because any handler can quietly violate the design.
Reactors work best when incoming events already have a stable shape. A handler should not need to guess whether an event is complete, whether fields are optional, or whether several unrelated event kinds were packed into one ambiguous payload.
Useful reactor events usually make these things explicit:
An event loop is the control mechanism. A reactor is the design pattern built around quick readiness-driven dispatch. Many reactors contain an event loop, but the reactor pattern is more specific about responsiveness and offloading.
Useful reactor metrics usually include:
These metrics show whether the reactor is still acting like a fast dispatcher or has started absorbing too much work.
Model a reactor in Clojure as a very small dispatch center with explicit handoff to workers or stages. The moment the central loop starts doing heavy work itself, the design has drifted away from the pattern.