Replacing Components
No system stays the same forever.
Over time, you will want — or be forced — to replace components:
- A database driver
- A persistence layer
- An authentication mechanism
- A logging backend
- A messaging system
- Even an entire service boundary
The critical question is not whether components will be replaced, but:
Can they be replaced without destabilizing the system?
Plumego is designed to make replacement possible — but only if its principles are respected.
First Principle: Replacement Is an Architectural Concern
Component replacement is not a refactoring trick.
It is an architectural capability.
If replacing a component requires:
- Touching business logic everywhere
- Changing handler signatures
- Modifying unrelated packages
- Introducing temporary global hacks
Then the system was already tightly coupled.
Plumego’s explicit boundaries exist largely to prevent this.
What Counts as a “Component”
In Plumego systems, components typically include:
- Infrastructure adapters (DB, cache, message broker)
- External service clients
- Authentication / authorization providers
- Serialization formats
- Observability backends
- Transport adapters (HTTP, WebSocket, RPC)
Components are defined by replaceability, not by size.
Why Components Need to Be Replaced
Common drivers include:
- Performance limitations
- Cost constraints
- Operational complexity
- Vendor lock-in
- New requirements
- Regulatory or security changes
Replacement is a sign of system maturity, not failure.
The Golden Rule: Replace Behind an Interface
The most important rule:
Components must be replaced behind explicit interfaces.
In Plumego, this usually means:
- Interfaces defined in the usecase or application layer
- Implementations living in infrastructure packages
- Wiring done at the composition root
Example:
type OrderRepository interface {
FindByID(ctx context.Context, id string) (Order, error)
Save(ctx context.Context, order Order) error
}
The interface belongs to the consumer, not the provider.
Step-by-Step Replacement Strategy
A safe replacement follows a predictable sequence.
Step 1: Stabilize the Contract
Before replacing anything:
- Identify the interface
- Make implicit assumptions explicit
- Add tests that describe current behavior
Do not change behavior yet.
Step 2: Introduce a Parallel Implementation
Add the new component alongside the old one.
Example:
repo/
postgres/
mysql/
Both implement the same interface.
No production traffic should use the new one yet.
Step 3: Wire Selectively
At the composition root:
- Add configuration flags
- Choose implementation explicitly
Example:
var repo OrderRepository
if cfg.UseMySQL {
repo = mysql.New(...)
} else {
repo = postgres.New(...)
}
This keeps the switch visible and reversible.
Step 4: Validate in Isolation
Before switching production traffic:
- Run integration tests against the new component
- Validate performance characteristics
- Observe error behavior
Replacement should reduce uncertainty, not increase it.
Step 5: Gradual Cutover
In production:
- Switch in controlled environments first
- Use canary or staged deployment
- Monitor closely
Avoid “big bang” replacements whenever possible.
Step 6: Remove the Old Component
Once confidence is established:
- Remove old code paths
- Remove compatibility layers
- Simplify wiring
Temporary duplication should not become permanent.
Replacing Infrastructure Components
Infrastructure is the easiest place to replace components — if boundaries are clean.
Examples:
- SQL database → different SQL database
- Redis → in-memory cache
- HTTP client → gRPC client
Key discipline:
- Do not leak driver-specific types upward
- Translate errors at boundaries
- Normalize behavior in adapters
If driver types appear in usecases, replacement will hurt.
Replacing Cross-Cutting Components
Replacing cross-cutting components (logging, auth, tracing) is harder.
Guidelines:
- Keep cross-cutting logic in middleware
- Inject dependencies explicitly
- Avoid global registries
- Avoid static singletons
Middleware is the correct replacement boundary for these concerns.
Replacing a Router or Transport
Replacing routing or transport is rare and risky.
If necessary:
- Introduce a new entry point
- Run both in parallel
- Route traffic explicitly
- Avoid modifying core lifecycle semantics
Never attempt to hot-swap core execution models.
What Not to Replace Incrementally
Some changes should not be incremental:
- Domain model rewrites
- Core invariants changes
- Semantic meaning of APIs
These require deliberate versioning strategies,
not silent replacement.
Replacement and Testing Strategy
Replacement requires tests at multiple levels:
- Unit tests for interface contracts
- Integration tests for new components
- Regression tests for existing behavior
- Observability validation in staging
Tests are the safety net that makes replacement possible.
Warning Signs of Poor Replaceability
If you observe:
- Extensive
if old { ... } else { ... }logic scattered everywhere - Temporary adapters that never get removed
- Business logic branching on implementation details
- Tests that depend on concrete implementations
Then boundaries are eroding.
Address that before replacing anything else.
Replacement vs Refactoring
Refactoring improves code without changing behavior.
Replacement changes how behavior is implemented.
Treat replacement with more caution, documentation, and visibility.
Summary
In Plumego systems:
- Components are meant to be replaced
- Replacement happens behind interfaces
- Wiring makes replacement explicit
- Changes are staged, not rushed
- Boundaries protect stability
A system that cannot replace components safely
will eventually be forced into a rewrite.
Plumego’s discipline exists to prevent that outcome.
Next
A natural follow-up topic is:
→ Advanced / Background Processing
This explores how to introduce asynchronous work
without breaking request semantics or lifecycle guarantees.