42 lines
2.6 KiB
Markdown
42 lines
2.6 KiB
Markdown
|
|
# ADR-003: No Middleware Installed by Default
|
||
|
|
|
||
|
|
**Status:** Accepted
|
||
|
|
**Date:** 2026-03-18
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
Many HTTP frameworks and server libraries install a default middleware stack — request logging, panic recovery, CORS headers, request ID injection — on the assumption that most applications will want these. The question is whether `httpserver.New(...)` should do the same.
|
||
|
|
|
||
|
|
Arguments for a default stack:
|
||
|
|
- Reduces boilerplate for the common case.
|
||
|
|
- Ensures recovery from panics is always in place.
|
||
|
|
|
||
|
|
Arguments against:
|
||
|
|
- Different applications need different middleware in different orders. Order matters: request ID must precede the logger so the logger can attach the ID; auth must precede RBAC; compression must follow auth.
|
||
|
|
- Installing middleware that the caller neither requested nor knows about makes the system harder to reason about and test.
|
||
|
|
- The micro-lib design principle is zero-surprise construction: `New()` produces a minimal, predictable value. Behavior is added explicitly.
|
||
|
|
- A middleware like `RequestLogger` needs a logger argument that `httpserver` would have to accept on behalf of `httpmw`. This couples configuration surface areas that should be independent.
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
`httpserver.New(...)` installs no middleware. The router returned from `New()` is a bare `chi.NewRouter()`. Middleware is composed explicitly by the caller using `WithMiddleware(...)`:
|
||
|
|
|
||
|
|
```go
|
||
|
|
srv := httpserver.New(logger, cfg,
|
||
|
|
httpserver.WithMiddleware(
|
||
|
|
httpmw.RequestID(uuid.NewString),
|
||
|
|
httpmw.Recover(),
|
||
|
|
httpmw.RequestLogger(&logAdapter{logger}),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
`WithMiddleware` is a variadic functional option that appends middleware to an internal slice. In `OnInit()`, each middleware is applied to the router via `s.Router.Use(mw)` in registration order (first registered = outermost in the chain).
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
- Every application must explicitly compose its middleware stack. This is intentional: the stack is visible, ordered, and testable in the application source.
|
||
|
|
- Panic recovery is not automatic. If a caller omits `httpmw.Recover()`, unhandled panics will crash the process. This is a deliberate trade-off — it makes the stack explicit and avoids silent behavior.
|
||
|
|
- Adding middleware after `OnInit()` has run is permitted by chi but is not the intended usage pattern. Route registration and middleware composition should both happen in the `BeforeStart` hook (before `OnStart` binds the port).
|
||
|
|
- The `WithMiddleware` option appends, so multiple calls accumulate: `WithMiddleware(a, b)` followed by `WithMiddleware(c)` results in `[a, b, c]` applied in that order.
|