Menu
Dev.to #systemdesign·October 21, 2026

Sacrificial Architecture: Designing for Evolvability and Planned Obsolescence

Sacrificial architecture is a strategic approach in system design that involves deliberately building components or services with the explicit intention of replacing them in the future. This pattern acknowledges the evolutionary nature of systems and business requirements, advocating for temporary solutions that are right for the current moment, rather than over-engineered "forever" versions. Key principles include isolating sacrificial components behind stable interfaces and protecting durable assets like the core domain model and data.

Read original on Dev.to #systemdesign

What is Sacrificial Architecture?

Sacrificial architecture is a design philosophy where certain system components are built with the explicit expectation of being discarded and rewritten within a defined timeframe. This approach contrasts with the common tendency to build every service as if it must last indefinitely, which often leads to legacy systems that are too entrenched and costly to replace even when they no longer fit the business needs. Drawing inspiration from Martin Fowler and Fred Brooks, it emphasizes that often the best code for today is not the code for tomorrow, especially in rapidly evolving domains.

Why Adopt a Sacrificial Approach?

The primary motivation for sacrificial architecture is to manage uncertainty and accelerate evolution. Early-stage features, integrations with volatile partners, or services built on unvalidated assumptions are prime candidates for this model. It avoids over-engineering for unknown future requirements, allowing teams to build faster and validate ideas without committing to long-term maintenance of potentially incorrect solutions. Examples like eBay's multiple rewrites and Google's 10x growth rule illustrate that evolution, not eternal stability, is often the path to success.

Key Components to Sacrifice vs. Preserve

  • Sacrifice: Components facing high uncertainty, experimental features, integrations with unstable partners, or services designed for speculative scale. These are built to be cheap to replace when assumptions change.
  • Preserve (Make Durable): Your core domain model (the fundamental concepts of your business) and your data. Data must always outlive the code that operates on it. Sacrificial services must not *own* durable data in a way that ties its lifecycle to the service itself. Data should reside in a separate, independently managed store.
⚠️

Sacrificial Does Not Mean Sloppy

A crucial distinction: sacrificial architecture is not an excuse for poor quality. The temporary service must still function reliably for its intended lifespan. "Planned deletion" means a strategic exit, not a license to skip testing or cut corners on internal quality. The only code that can be less polished is that which is literally being retired in the immediate term.

Enabling Deletion: The Importance of Seams and Boundaries

The ability to actually delete a sacrificial service hinges on strict architectural boundaries and stable contracts. Services should expose a clear, durable port (interface) that describes *what* they do, abstracting away the *how*. This allows for replacement implementations to be swapped in seamlessly, often using techniques like the Strangler Fig pattern for gradual cutovers. The underlying data and the service's internals must never be directly coupled to external consumers. Code example demonstrates how an interface (port) allows different implementations to be swapped without affecting callers:

typescript
// The port is the durable thing. The implementation behind it is sacrificial.
interface RiskScorer {
  score(tx: Transaction): Promise<RiskDecision>;
}

// v1: a cheap heuristic you built in a week, fully expecting to bin it.
class HeuristicRiskScorer implements RiskScorer {
  /* rules, thresholds */
}

// v2: the ML service you'll build once you actually understand the problem.
class ModelRiskScorer implements RiskScorer {
  /* calls the model service */
}

// Callers depend on RiskScorer, never on which one is wired in.
// Swapping v1 for v2 is a config change and a gradual traffic shift, not a rewrite.
architectural patternsevolvabilityrefactoringlegacy systemsdomain driven designstrategic planningtechnical debt

Comments

Loading comments...