Decorator Pattern with Function Wrapping in Clojure

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.

Function Wrapping Is the Native Form

 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.

Decoration Should Preserve Core Meaning

Good decorators add concerns such as:

  • tracing
  • metrics
  • caching
  • authorization checks
  • retries or circuit-aware guards

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.

Order Matters More Than It Looks

Decorator stacks are composable, but composition order changes behavior.

For example:

  • logging outside authorization sees denied requests
  • logging inside authorization sees only allowed requests
  • timing outside retries measures total retry cost
  • timing inside retries measures only one attempt

That means decorator order should be intentional, not accidental.

Middleware Is Just a Specialized Decorator Stack

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:

  • what behavior belongs around the handler?
  • what behavior belongs inside the domain logic?

Keep Decorators Small and Honest

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:

  • one focused responsibility
  • explicit naming like wrap-authz or wrap-metrics
  • minimal assumptions about the wrapped function
  • behavior that stays readable in composition order

Bad signs:

  • hidden mutation
  • swallowed exceptions without a clear contract
  • wrappers that change return shapes unpredictably

Common Failure Modes

Too Many Wrappers with No Clear Order

The system may still “work,” but nobody understands which layer is responsible for what.

Cross-Cutting Concerns Leaking Domain Policy

A decorator should not quietly become the place where core business rules live.

Decorators That Change Contracts Unpredictably

If one wrapper suddenly changes the result shape or exception behavior, composition gets fragile.

Practical Heuristics

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.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026