Explicit Middleware Chains
Middleware is powerful — and dangerous.
It can:
- Enforce cross-cutting concerns
- Centralize behavior
- Reduce duplication
But when middleware chains become implicit or uncontrolled, they introduce:
- Hidden execution order
- Surprising side effects
- Debugging nightmares
- Accidental coupling
In Plumego, middleware must be explicit, intentional, and readable.
This document defines a pattern for building middleware chains that remain understandable as systems grow.
The Core Problem with Middleware
Middleware sits between requests and handlers.
That makes it easy to add behavior —
and easy to hide it.
Common failure modes include:
- “Why is this handler returning 401?”
- “Where did this header come from?”
- “Why does logging sometimes miss requests?”
- “Why did adding middleware X break feature Y?”
The root cause is almost always the same:
The middleware chain is implicit.
The Explicit Middleware Chain Principle
The pattern is simple:
The full middleware chain for a request must be visible and predictable.
This implies:
- Order is intentional
- Scope is explicit
- Responsibilities are clear
- Side effects are known
Middleware should never feel magical.
Middleware Is Control Flow
Middleware is not just “helpers”.
It defines control flow:
- Whether a request proceeds
- When logic executes
- What context is available
- How errors are handled
Therefore, middleware deserves the same rigor as core application logic.
Classifying Middleware by Responsibility
A practical way to keep chains explicit is to classify middleware by role.
Common middleware categories
Request identity
- Trace ID
- Request ID
- Correlation metadata
Observability
- Logging
- Metrics
- Tracing hooks
Safety
- Panic recovery
- Rate limiting
- Timeouts
Security
- Authentication
- Authorization (coarse-grained)
Transport concerns
- CORS
- Content-type enforcement
Mixing these categories without structure leads to confusion.
A Recommended Middleware Order
While exact order may vary, a clear default order should exist.
A commonly effective order in Plumego systems:
- Trace ID
- Logging
- Panic Recovery
- Rate Limiting / Timeouts
- Authentication
- Authorization (coarse)
- Handlers
This order ensures:
- Every request is traceable
- Failures are logged
- Panics are contained
- Security checks are predictable
The important part is not the exact order —
it is that the order is deliberate and documented.
Making Middleware Order Visible in Code
Avoid patterns where middleware is scattered across files or auto-registered.
Prefer a single, readable composition point.
Example:
app := plumego.New()
app.Use(
TraceIDMiddleware(),
LoggingMiddleware(logger),
RecoveryMiddleware(logger),
RateLimitMiddleware(),
JWTAuthMiddleware(verifier),
)
This makes the execution chain immediately visible.
If you cannot understand request flow by reading this block,
the chain is already too implicit.
Avoiding Conditional Middleware
Conditional middleware is a common source of hidden behavior.
Example to avoid:
if config.EnableAuth {
app.Use(AuthMiddleware())
}
This creates environment-dependent behavior that is hard to reason about.
If conditional behavior is required, make it explicit:
- Separate app instances
- Separate routers
- Separate startup paths
Global vs Route-Scoped Middleware
Not all middleware belongs globally.
Global middleware
- Trace ID
- Logging
- Panic recovery
These apply to every request.
Route-scoped middleware
- Authentication
- Authorization
- Feature-specific behavior
Apply them explicitly at route definition time.
Example:
app.Group("/admin",
AuthMiddleware(),
AdminOnlyMiddleware(),
).GET("/stats", StatsHandler)
Scope should be obvious from the route definition.
Middleware Should Be Boring
A good middleware:
- Does one thing
- Has clear inputs and outputs
- Avoids hidden state
- Is easy to remove
Middleware that tries to be “clever” usually becomes a liability.
Avoiding Middleware That Encodes Business Logic
Middleware should not:
- Decide business outcomes
- Encode domain rules
- Perform workflow orchestration
Those belong in handlers and usecases.
Middleware should prepare the ground, not run the business.
Debugging Explicit Middleware Chains
Explicit chains make debugging straightforward:
- You know the order
- You know which middleware ran
- You know where a request stopped
This dramatically reduces the cost of production debugging.
Middleware and Testing
Explicit chains improve testability:
- Middleware can be tested in isolation
- Chains can be tested end-to-end
- Order-related bugs are easier to catch
Implicit chains are difficult to test reliably.
Common Anti-Patterns
Implicit Registration via init()
This hides behavior and makes ordering unpredictable.
Middleware That Mutates Global State
This breaks request isolation and causes race conditions.
Middleware That Does Too Much
Large middleware functions are a sign of mixed responsibilities.
Summary
In Plumego:
- Middleware defines control flow
- Control flow must be explicit
- Order must be intentional
- Scope must be visible
- Behavior must be predictable
Explicit middleware chains are one of the most effective ways
to keep a system understandable as it grows.
Next
With explicit middleware chains in place, the next useful pattern is:
→ Explicit Error Mapping
This explains how to keep error handling consistent without scattering logic across handlers.