Custom Router
Plumego’s built-in router is intentionally simple, explicit, and boring.
For the vast majority of systems, it is more than sufficient.
However, in a small number of advanced scenarios, you may need to introduce
custom routing behavior — without breaking Plumego’s core guarantees.
This document explains:
- When a custom router is justified
- When it is absolutely not
- How to introduce one safely
- What constraints must never be violated
First Principle: The Default Router Is the Baseline
Before considering a custom router, internalize this rule:
If you cannot clearly explain why the default router is insufficient, you should not replace or extend it.
The default router already provides:
- Deterministic method + path matching
- Explicit registration
- Clear middleware scoping
- Predictable dispatch
Most “router problems” are actually architecture or middleware problems.
Legitimate Reasons for a Custom Router
A custom router may be justified if all of the following are true:
1. The requirement is structural, not stylistic
Valid examples:
- Extremely high-cardinality dynamic routing (e.g. multi-tenant host-based routing)
- Protocol multiplexing on a single port
- API gateway–style dispatch based on headers or subprotocols
- Specialized performance constraints proven by profiling
Invalid examples:
- “I prefer a different syntax”
- “This other framework does it differently”
- “I want auto-generated routes”
2. The behavior cannot be expressed via middleware + handlers
If middleware and handlers can solve the problem cleanly,
introducing a custom router is unnecessary.
Routing is about selection, not decision logic.
3. The routing decision must happen before handler selection
If the decision can happen inside a handler,
you do not need a custom router.
What a Custom Router Must Not Do
Any custom routing logic must preserve Plumego’s core guarantees.
It must not:
- Add hidden lifecycle phases
- Introduce implicit middleware
- Perform authentication or authorization
- Execute business logic
- Create multiple handlers for one request
- Retry or fork request execution
If any of these happen, the router is no longer a router.
Supported Extension Model
Plumego does not support pluggable router engines via magic hooks.
Instead, the supported model is:
Wrap or replace routing logic explicitly at the HTTP handler boundary.
This keeps behavior visible and testable.
Pattern 1: Router as a Pre-Dispatcher
In this pattern, you introduce a pre-router that decides
which Plumego app or handler should receive the request.
Example use cases:
- Host-based routing
- Protocol negotiation
- Multi-app composition
Conceptual example:
type MultiAppRouter struct {
apps map[string]http.Handler
}
func (r *MultiAppRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
host := req.Host
if app, ok := r.apps[host]; ok {
app.ServeHTTP(w, req)
return
}
http.NotFound(w, req)
}
Each app can be a fully independent Plumego instance.
Plumego’s internal lifecycle remains untouched.
Pattern 2: Router as a Header-Based Dispatcher
In some advanced API gateway scenarios,
routing decisions depend on headers rather than paths.
Example:
X-API-VersionX-Tenant-ID- Subprotocol negotiation
You can implement this outside Plumego:
func VersionRouter(v1, v2 http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Header.Get("X-API-Version") {
case "v2":
v2.ServeHTTP(w, r)
default:
v1.ServeHTTP(w, r)
}
})
}
Each handler may be a Plumego app with its own routing table.
Pattern 3: Internal Router Replacement (Rare)
In very rare cases, you may want to replace Plumego’s router internally.
This should only happen if:
- You fully control the Plumego fork
- You are willing to maintain it
- You have strict performance requirements
- Profiling proves routing is the bottleneck
Even then, your replacement must:
- Preserve method + path semantics
- Preserve deterministic matching
- Preserve middleware ordering
- Preserve lifecycle guarantees
This is advanced framework work, not application code.
What Not to Do
Do Not Encode Business Logic in Routing
Routing selects where execution goes,
not what happens.
If routing decisions depend on domain rules,
those rules belong in usecases.
Do Not Create “Smart” Routers
Routers that:
- Validate input
- Authorize users
- Transform payloads
Become untestable and unpredictable.
Do Not Mix Routing and Middleware Concerns
Middleware wraps execution.
Routing selects execution.
Blurring this line leads to hidden behavior.
Testing Custom Routing
Custom routing must be tested independently.
Recommended tests:
- Routing decision tests (input → selected handler)
- Fallback behavior
- No-route behavior
- Performance benchmarks (if performance is the reason)
Do not rely on integration tests alone.
Maintenance Cost Warning
Every custom router introduces:
- Additional cognitive load
- Additional maintenance burden
- Upgrade friction
- Onboarding complexity
Before introducing one, ask:
“Is this complexity truly unavoidable?”
Most of the time, the answer is no.
Summary
In Plumego:
- The default router is intentionally sufficient
- Custom routers are an advanced, rare tool
- Routing must remain selection-only
- Lifecycle guarantees must be preserved
- Explicitness always wins over cleverness
If you need a custom router, build it around Plumego,
not inside it — unless you are prepared to own the consequences.
Next
A natural continuation after routing customization is:
→ Advanced / Performance Considerations
This explains how to reason about throughput, latency, and allocation
without sacrificing clarity or correctness.