Learn how higher-order functions let Clojure add logging, metrics, authorization, and other cross-cutting behavior without mutating the wrapped function's core logic.
Decorator pattern: A structural pattern that adds behavior around an existing component without rewriting the component itself.
In Clojure, decorators are usually just higher-order functions. That makes the pattern more direct than in object-oriented examples. Instead of subclassing or wrapping an object tree, you often wrap a function, handler, or service boundary with behavior such as logging, timing, authorization, or retry logic.
1(defn wrap-timing [f]
2 (fn [& args]
3 (let [started (System/nanoTime)
4 result (apply f args)
5 elapsed-ms (/ (- (System/nanoTime) started) 1e6)]
6 (println "elapsed-ms:" elapsed-ms)
7 result)))
8
9(defn total [prices]
10 (reduce + prices))
11
12(def timed-total (wrap-timing total))
The important property is that the wrapped function still exposes the same basic responsibility. The decorator adds cross-cutting behavior around it.
Good decorators add concerns such as:
They should not quietly replace the business meaning of the wrapped function. If the wrapper starts owning core workflow rules, it is no longer just decoration.
Decorator stacks are composable, but composition order changes behavior.
For example:
That means decorator order should be intentional, not accidental.
Ring middleware is one of the clearest Clojure examples of decorator composition. Each wrapper takes a handler and returns a new handler. That is classic decorator structure expressed in functional form.
Once you see that, the pattern becomes less about naming and more about boundaries:
Strong decorators are easy to reason about because they do one cross-cutting job well. A weak decorator often becomes a dumping ground for several unrelated concerns.
Good signs:
wrap-authz or wrap-metricsBad signs:
The system may still “work,” but nobody understands which layer is responsible for what.
A decorator should not quietly become the place where core business rules live.
If one wrapper suddenly changes the result shape or exception behavior, composition gets fragile.
Use decorators for cross-cutting concerns that should stay outside the main business function. Keep wrappers small, name them by responsibility, and be explicit about composition order. In Clojure, the pattern is not an exotic class structure. It is disciplined use of higher-order functions.