The Decorator pattern wraps an object to add behavior, keeping the same interface. In Go, this pattern is everywhere: it's how HTTP middleware works. Any function that takes an interface and returns the same interface, adding behavior in between, is a decorator.
The canonical Go example is http.Handler middleware: a function that takes a handler, returns a new handler that logs, authenticates, compresses, or rate-limits, and then calls the original.
It's the Open/Closed Principle.
Scenario
You have an HTTP handler that serves an API. You need to add logging. Then authentication. Then CORS headers. Each concern is independent, but you don't want to stuff all of them into one giant handler. And you want to compose them differently for different routes.
// fat_handler.go
package api
import (
"log"
"net/http"
"time"
)
func handleItems(w http.ResponseWriter, r *http.Request) {
// Authentication check (shouldn't be here)
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "unauthorised", 401)
return
}
// Logging (shouldn't be here)
start := time.Now()
defer func() {
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
}()
// CORS (shouldn't be here)
w.Header().Set("Access-Control-Allow-Origin", "*")
// Actual response — buried under cross-cutting concerns
w.Write([]byte("items: []"))
}Every cross-cutting concern is tangled into the handler. Want logging on another route? Copy-paste. Want auth on some routes but not others? Conditionals. The response logic is obscured by plumbing.
Solution
Each concern becomes a middleware function: it takes an http.Handler, returns a new http.Handler that adds one behavior, and calls the original. Stack them like function composition.
Request ──► Logging ──► Auth ──► CORS ──► Handler
│ │ │ │
wraps wraps wraps actual
handler handler handler logic
Each layer: func(http.Handler) http.Handlerpackage gomark
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"time"
)
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
http.Error(w, "unauthorised", http.StatusUnauthorised)
return
}
next.ServeHTTP(w, r)
})
}
func CORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
next.ServeHTTP(w, r)
})
}
func itemsHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("items: []"))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
func send(handler http.Handler, path, token string) {
req := httptest.NewRequest("GET", path, nil)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
fmt.Printf("%s %d %s\n", path, rec.Code, rec.Body.String())
}
func main() {
items := Logging(Auth(CORS(http.HandlerFunc(itemsHandler))))
health := Logging(http.HandlerFunc(healthHandler))
send(items, "/items", "token") // 200
send(items, "/items", "") // 401 — no auth
send(health, "/health", "") // 200 — no auth required
}Output:
/items 200 items: []
/items 401 unauthorised
/health 200 okWhen to Use
- You need to add behavior to objects without modifying their code.
- You want to compose behaviors independently: different combinations for different cases.
- The behavior is cross-cutting (logging, auth, caching, metrics) and shouldn't live in business logic.
- You see yourself wrapping an
http.Handler. You're already using Decorator.
When Not to Use
- The added behavior is tightly coupled to the object's internals. A decorator that needs private fields isn't a decorator; it's a refactoring need.
- Deep decorator stacks (5+ layers) make debugging difficult. Consider whether a Chain of Responsibility would be clearer.
- You only ever need one fixed combination. Direct composition in a single handler might be simpler.
The Decision
The function-wrapper form is idiomatic Go: returning http.HandlerFunc(func(...) {...}) adds almost no boilerplate and every Go developer recognizes it instantly. The cost that accumulates is order sensitivity. Logging(Auth(handler)) logs all requests including rejected ones; Auth(Logging(handler)) only logs authenticated traffic. Small difference, large operational impact, and the compiler won't warn you either way.
Stack traces through multiple anonymous closures also become hard to read. When Auth short-circuits inside a chain five wrappers deep, the request path in logs shows nothing useful. Name your handler functions rather than using anonymous closures, and keep chains to three or four layers.
Related Patterns
- Adapter: Adapter resolves an interface mismatch; Decorator keeps the same interface and adds behavior. If your wrapper changes the API, it's an Adapter; if it preserves the API and enriches it, it's a Decorator.
- Composite: Decorator wraps exactly one object and adds behavior; Composite aggregates many objects of the same type. If you wrap one, Decorator; if you compose many, Composite.
- Proxy: Proxy and Decorator are structurally identical in Go; the distinction is intent. Proxy controls or intercepts access (lazy init, auth, caching); Decorator adds new capabilities without restricting access.
- Chain of Responsibility: HTTP middleware chains are both Decorator and Chain of Responsibility: each middleware wraps the next (Decorator) and may short-circuit the chain without calling the inner handler (Chain of Responsibility).