Refactor or Rebuild: A Decision and Migration Protocol

Choose refactoring, incremental replacement or a bounded rebuild from explicit constraints, then define write ownership, reconciliation and rollback limits.

Decide which constraint must change

A system can be difficult to work in for several reasons: unclear business rules, tightly coupled modules, unsupported dependencies, unreliable deployments or a data model that cannot express a new requirement. Each points toward a different intervention.

Start with the outcome the existing system cannot reliably provide. Show the evidence and identify what must remain unchanged for users. Age, language preference and a code-quality score are insufficient reasons to approve replacement.

This guide compares three modernization paths and defines the evidence needed to choose one. Use the legacy-modernization guide for broader sequencing across a portfolio.

There is no universal rewrite failure rate or health-score cutoff used here. The worked migration below is hypothetical, not an anonymous client case study.

Separate refactoring from behavior change

In its technical sense, refactoring changes internal structure while preserving observable behavior. Replacing an API contract, changing billing rules or moving to a different consistency model is more than refactoring, even if the project uses that label.

Characterization tests help expose the behavior the current system implements. They do not establish that every observed behavior is desirable. Record intentional bug fixes separately, agree their acceptance criteria and preserve the ability to distinguish a behavior change from a structural regression.

Replacement has two broad delivery forms. An incremental replacement moves a defined capability while old and new implementations coexist. A bounded rebuild replaces a defined system or component and uses an explicit cutover plan. A single cutover can be feasible when scope and state are understood; it is not automatically safe or automatically doomed.

OptionConditions that make it worth testing
Refactor in placeThe required behavior and data model remain suitable; an internal change can remove the observed constraint with testable compatibility.
Incremental replacementA meaningful boundary can be separated, coexistence can be operated and data ownership can be transferred without conflicting writers.
Bounded rebuildThe scope is sufficiently known, replacement is feasible, and cutover, data migration and recovery have acceptable limits.
Retain or retireThe expected value does not justify change, or a business decision removes the need for the capability. Acceptance or retirement still needs an owner.

A system may use several options. Refactor a boundary to make extraction possible, replace one component, and retain another until its planned retirement.

Build a decision brief before selecting a pattern

Collect evidence that could disprove the preferred approach.

Decision questionEvidence to bring
What is failing now?Specific blocked changes, operational failures or verified support constraints, with frequency and impact.
Which behavior matters?Critical workflows, external contracts, historical edge cases and business-owner acceptance.
Where is the state?Writers, readers, transactions, batch jobs, replicas, external side effects and retention requirements.
Can a slice stand alone?Dependency and transaction boundaries, not just a convenient directory or service name.
What must coexist?Old clients, in-flight work, data formats, access controls and reporting dependencies.
What can the team operate?Delivery and recovery evidence, specialist availability, dual-running burden and explicit ownership.
What is the useful horizon?Product plans, support deadlines, expected demand and the cost of keeping both systems alive.

A missed deployment target is a reason to investigate delivery risk, not a universal rule to rebuild or spend a fixed percentage on cleanup. Likewise, an old language can remain supportable, while a recently adopted component can already be unsuitable.

Estimate implementation, validation, migration, dual operation, training and retirement costs. Compare ranges against a finite horizon using the technical-debt cost worksheet. Keep regulatory or security obligations separate from speculative payback.

Test a boundary before committing the program

Select a discovery slice that tests a material uncertainty. If the team doubts that pricing rules can be separated, extracting an unrelated static page does not answer the question.

For an in-process dependency, branch by abstraction describes putting implementations behind an abstraction while changes continue. The abstraction must preserve the required semantics, and both implementations need evidence under the same cases.

For independently routed capabilities, the strangler fig pattern supports coexistence through a routing boundary. That boundary can itself become a failure point. It also cannot split a transaction safely merely because two endpoints have different URLs.

Define what discovery delivers: a tested contract, dependency map, migration rehearsal and revised option estimate. Stop before building an entire replacement if the proposed boundary proves invalid.

Worked migration: transfer catalog-description ownership

Consider a fictional catalog application. The proposed slice owns product descriptions and their revision history. Pricing, stock and checkout remain in the existing system. A stable product identifier connects them.

The goal is to let the catalog team change description behavior independently. That is a hypothesis to test, not a promised delivery-speed increase.

First inventory every writer, including administrative tools, imports and scheduled jobs. A public API facade does not control a batch job that writes directly to the database.

Agree an ownership rule: for a given product description, one system accepts authoritative writes at a time. A projection in the other system is read-only for application purposes. Enforce that rule at the write boundary, using a fenced ownership version or equivalent mechanism so a stale router cannot reactivate the former writer.

Use the following staged protocol:

  1. Capture representative current behavior and acceptance cases, including revisions, permissions and invalid updates.
  2. Establish a consistent snapshot and change-capture checkpoint. Backfill the new read model while the old application remains the sole writer.
  3. Apply subsequent changes with stable identifiers, version handling and duplicate protection. Track lag and investigate missing or out-of-order records.
  4. Compare the old and new views using the same authorized requests. Shadow reads must not trigger writes, messages or external calls with side effects.
  5. Rehearse transfer for a bounded cohort. Fence the old writer, drain or account for in-flight updates, apply the remaining changes, reconcile, then enable the new writer.
  6. Observe correctness and operations before expanding. Keep an explicit owner for rejected writes and discrepancies.

The pause or fencing mechanism must be tested against concurrent requests and retries. Do not claim zero downtime if a safe transfer requires a write pause. State that constraint and agree it with the business.

An outbox can record the intent to publish alongside a local state change. Change data capture may be another source of updates. Either mechanism still needs delivery monitoring, idempotent application and reconciliation. Subscribing to events is not, by itself, event sourcing; event sourcing uses the event history as the authoritative record from which state is derived.

Avoid unspecified bidirectional synchronization. If both applications accept independent edits, conflict resolution, ownership and ordering become part of the business design, not an implementation detail.

Write the rollback boundary before transferring writes

Traffic routing is only one part of recovery. Once the new system accepts writes, the old system may not contain or understand them.

Migration stateRecovery condition
Old system writes; new system only projectsRoute reads back after confirming the old system remains authoritative. Investigate incomplete projection independently.
New system writes compatible dataBefore returning write ownership, fence new writes, reconcile all accepted changes into the old representation and verify compatibility. Do not enable both writers.
New behavior or schema cannot be represented by the old systemA routing rollback is unavailable. Use the approved forward-recovery or data-conversion plan, or do not cross this gate yet.
Historical data or old infrastructure is removedRecovery depends on validated retained data and restore capability. Deletion is a separate approval, not an automatic step after traffic reaches the new service.

During the reversible period, the example could maintain a one-way compatibility projection from the new writer to the old store. The previous old-to-new propagation must not create a loop. Consumers need provenance or version rules that distinguish authoritative changes from replicated ones.

A backup is not a tested recovery plan. Rehearse restore into an isolated target, validate its contents, measure the recovery time and determine what accepted work occurred after the backup. Restoring an older database over active production can discard valid writes and create inconsistencies with external systems.

Keep the old implementation available only for a justified recovery window. Give that window an end condition so dual operation does not become indefinite.

Approve a slice on evidence

Before expanding the catalog migration, require evidence that:

  • Authoritative writes reach exactly the intended owner, including imports and administrative paths.
  • Accepted changes reconcile by identifier, revision and meaningful field values, not only aggregate row counts.
  • Duplicate delivery and retries do not produce duplicate business effects.
  • Permission checks and tenant boundaries match the agreed contract.
  • Failure detection, escalation and recovery work under the rehearsed conditions.
  • The team can identify what has migrated and which dependencies still need the old system.

No fixed traffic percentage or number of quiet days proves completion. Choose exposure and observation from representative use, failure impact and the ability to detect and contain a problem.

Retirement also requires checking reports, integrations, retained history and scheduled work that request metrics may not reveal.

Make the next decision smaller

Bring one completed decision brief and one tested boundary to a refactor-versus-rebuild assessment. Ask which option removes the observed constraint with acceptable migration and operating risk.

The useful outcome is a funded next slice, an explicit acceptance decision or a decision to stop. A new architecture diagram without write ownership and recovery evidence is not enough to authorize a rebuild.

Related services