Embedding Plumego
Plumego is designed to be embedded, not monopolistic.
It does not assume it owns the process.
It does not assume it owns the server lifecycle.
It does not assume it is the only HTTP surface.
This makes Plumego particularly suitable for integration-heavy systems —
but only if embedding is done intentionally.
This document explains how to embed Plumego without breaking its guarantees.
What “Embedding” Means
Embedding Plumego means:
- Plumego is one component of a larger system
- It coexists with other HTTP handlers, services, or runtimes
- It does not control process startup or shutdown
- It operates under externally defined constraints
Common embedding contexts include:
- Monolithic applications with multiple subsystems
- API gateways or edge services
- Existing legacy
net/httpservers - Admin / internal tools mounted alongside other handlers
- Multi-protocol servers (HTTP + metrics + debug endpoints)
Core Constraint: Plumego Remains an http.Handler
The most important rule:
Plumego must remain a normal
http.Handler.
Embedding should never require:
- Forking Plumego
- Reaching into internal state
- Hooking undocumented lifecycle points
- Sharing internal Context across systems
If embedding requires any of these, the approach is wrong.
Pattern 1: Mounting Plumego Under a Path Prefix
The most common embedding pattern is path-based mounting.
Example:
plumeApp := plumego.New()
// register routes
mux := http.NewServeMux()
mux.Handle("/api/", http.StripPrefix("/api", plumeApp))
mux.Handle("/metrics", metricsHandler)
mux.Handle("/healthz", healthHandler)
In this model:
- Plumego handles only
/api/* - Other endpoints remain outside Plumego
- Routing responsibilities are explicit
Plumego’s internal router is unchanged.
Pattern 2: Embedding Multiple Plumego Apps
You may embed multiple independent Plumego instances in one process.
Example:
publicAPI := buildPublicAPI()
adminAPI := buildAdminAPI()
mux := http.NewServeMux()
mux.Handle("/api/", publicAPI)
mux.Handle("/admin/", adminAPI)
Each Plumego app:
- Has its own middleware chain
- Has its own routing table
- Has its own configuration slice
This is often cleaner than overloading a single app.
Pattern 3: Plumego Inside a Larger Router
Sometimes Plumego is just one branch of a more complex router.
Example scenarios:
- Host-based routing
- Version-based routing
- Protocol multiplexing
Example:
func rootRouter(plume http.Handler, legacy http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/v2/") {
plume.ServeHTTP(w, r)
return
}
legacy.ServeHTTP(w, r)
})
}
Plumego remains unaware of the larger routing logic.
Pattern 4: Embedding in Long-Lived Processes
Plumego works well in processes that:
- Run background workers
- Maintain in-memory state
- Host multiple protocols
Important rules:
- Do not share Plumego Context outside request scope
- Do not assume request lifecycle aligns with process lifecycle
- Keep shutdown orchestration outside Plumego
Plumego handles requests — nothing more.
Configuration Boundaries When Embedding
Embedding often exposes configuration pitfalls.
Best practices:
- Slice configuration per embedded app
- Avoid global configuration structs
- Inject only what each Plumego app needs
Example:
publicCfg := cfg.PublicAPI
adminCfg := cfg.AdminAPI
Do not let embedding collapse configuration boundaries.
Logging and Observability Integration
When embedding Plumego:
- Prefer shared logging infrastructure
- Inject loggers into middleware
- Use consistent trace/Request IDs across handlers
Plumego should participate in observability,
not redefine it.
Avoid per-app logging systems unless isolation is required.
Authentication and Security Boundaries
Embedding multiple subsystems raises security questions.
Rules of thumb:
- Do not rely on path prefixes alone for security
- Use explicit middleware per embedded app
- Assume nothing about upstream handlers
Each Plumego app should enforce its own security invariants.
Lifecycle and Shutdown Coordination
Because Plumego does not own the server lifecycle:
- Shutdown signals must be handled externally
- Server shutdown must be coordinated at the process level
- Plumego apps do not need to know about shutdown unless they hold resources
Use standard Go patterns (context.Context, http.Server.Shutdown).
What Not to Do When Embedding
Do Not Share Plumego Context Across Systems
Context is request-scoped and framework-specific.
Do Not Inject External State into Context
Embedding should not turn Context into a global bus.
Do Not Assume Execution Order Across Handlers
Only the HTTP server defines dispatch order.
Do Not Hide Plumego Behind Magical Adapters
If you cannot see where Plumego is mounted,
debugging will suffer.
Testing Embedded Plumego Apps
Testing strategies:
- Test Plumego apps standalone with
httptest - Test embedding routers separately
- Add a small number of integration tests for full wiring
Avoid testing everything through the top-level server.
Maintenance and Evolution
Embedding Plumego cleanly provides long-term benefits:
- Subsystems can evolve independently
- Routing changes are localized
- Migration paths remain open
- Framework upgrades are low-risk
Poor embedding choices make refactoring painful.
Summary
When embedding Plumego:
- Treat it as a normal
http.Handler - Keep boundaries explicit
- Mount, don’t merge
- Coordinate lifecycle externally
- Preserve request-scoped guarantees
Plumego is designed to fit into larger systems —
but only if you let it remain what it is.
Next
A natural follow-up topic is:
→ Advanced / Performance Considerations
This explores how embedding and scale interact with throughput,
latency, and resource usage.