Logo
Logo
  • Home
  • About
  • Services
    • Fractional CTO
    • Application Architecture
    • Platform Audits
    • Platform Rebuilds
  • Projects
  • Writing
  • Work
  • Request Quote
Logo

Backed by 20+ years of hands-on software development expertise, mithra62 transforms ideas into powerful, reliable solutions—designed to work exactly how you want, every time.

  • Address

    Tucson, AZ 85712
  • Email

    eric@mithra62.com
  • Contact

    +1-310-739-3322

Phase 8: The Rewrite

  • Home
  • Writing
Phase 8: The Rewrite
08 Jul 26
  • Brain Dump
  • Programming
  • Rant

Rewriting software sounds fun right up until you remember the old platform still has customers.

This is the clarifying fact that separates the rewrite conversation from the rewrite itself. In the conversation, the legacy system is the problem to be solved. In the rewrite, it is the environment you are operating inside, the constraint you cannot ignore, the thing that has to keep working while you build its replacement. You cannot take it offline. You cannot freeze its behavior while you catch up to it. You cannot ask your customers to wait while you sort out the architecture.

Nothing clarifies an architecture discussion quite like realizing the legacy billing system processes eight million dollars annually and cannot be turned off for a weekend.

The rewrite is not an engineering project that happens to have a migration component. It is a migration project that happens to involve new engineering. The distinction matters because it changes what success requires, what the risks are, and how long it actually takes. Organizations that understand this going in have a meaningfully better chance than those that discover it six months in.

The Dream of the New System

In the early weeks, the optimism is real and it is earned.

The new codebase is clean. The domain model has been designed with the knowledge of what the system actually needs to do, informed by years of operating the previous version. The abstractions are clear. The naming is deliberate. The service layer exists because it was planned, not because a controller grew past the point of manageability and someone extracted pieces of it into a class named UserHelper. The test coverage exists from the start, because this time there is time for tests, because the team has committed to doing this properly.

The database schema is normalized correctly. The multi-tenancy that was impossible to retrofit into the old system is built into the foundation. The authentication system uses the framework's built-in mechanisms rather than the custom session handler that has been a source of subtle bugs since 2017. The Eloquent models are lean. The jobs are small. The service providers are legible. The whole thing fits in a developer's head in a way the old system stopped doing years ago.

The team is energized in a way they have not been in a long time. Working in a system that makes sense, that does what you expect, that does not surprise you with side effects from three layers away, is a different experience from the one they have grown accustomed to. That feeling is real and it matters. It is also, if not carefully managed, the thing that will cause the project to underestimate everything that comes next.

The Hidden Complexity of Legacy Systems

At some point in every rewrite, a developer opens a piece of the old system they have not looked at closely before, and they find something that should not be possible.

A billing calculation that applies a discount based on a condition that is not exposed anywhere in the UI, has no corresponding database flag, and exists because of a handshake agreement with a customer in 2019 that was never formally documented. An import pipeline that silently corrects malformed data before processing it, because a specific integration partner sends malformed data, and at some point someone added a fix rather than asking the partner to change their export. A scheduled job that runs at 3am, fires a webhook to a third-party service, receives a response, and updates seven records based on parsing logic that appears nowhere else in the codebase. No ticket. No comment. Just code that has been running quietly in production for three years, handling something nobody on the current team knows needs to be handled.

The most dangerous code is often the code nobody realizes is important.

The old system is not just the features on the roadmap. It is the accumulated behavior of a platform that has been in contact with reality for years, shaped by customer requests, production incidents, integration quirks, and one-off fixes that became permanent. None of that behavior is in the requirements document for the new system, because there is no requirements document that captures all of it. Most of it will be discovered during the migration, when the new system produces a result that is different from what the old one produced, and someone has to figure out which one is correct, and the answer is almost always the old one.

This is why rewrite timelines are wrong in a specific direction. The engineering work is usually estimated reasonably well. The discovery work, the weeks spent reverse-engineering behavior that should have been documented years ago, is almost never estimated at all.

Parallel Systems and Migration Pain

For a period of time, both systems exist simultaneously. This is not a transitional phase to get through quickly; it is an extended operational reality that requires deliberate engineering to manage.

The dual-write pattern, where writes go to both the old and new data stores, is the most common approach and also the most demanding to maintain correctly. Every write path in the legacy system needs to be identified and mirrored. The data models between the two systems are not identical, because if they were identical you would not need a new system, which means every write requires a transformation, and the transformation logic is new code that has its own bugs. When the two systems disagree about the state of a record, which they will, someone has to decide which one is authoritative, and the answer changes depending on which phase of the migration that record is in. And, oftentimes, by someone who shouldn't be making such a decision.

Feature flags route traffic between the old and new implementations, which sounds clean in a diagram and becomes a combinatorial problem in practice. The new user creation flow is live, but the new billing flow is not, which means a user created in the new system needs to bill through the old one, which means the two systems need to share session state, or token state, or enough context to hand off cleanly, and the handoff is where the bugs live. The rollback strategy that seemed straightforward at the start of the project becomes harder to execute the further the migration progresses, because data written to the new schema is increasingly difficult to reconcile back to the old one.

Phased migrations are slower than anyone wants them to be. The correct pace is the one that allows the team to validate each phase before advancing, which means validating behavior under real production load, which means waiting. The pressure to move faster is constant. The risk of moving faster is that a bug in the migration logic corrupts data across both systems simultaneously, which is a different category of problem than a bug that corrupts data in one.

The Risk of Recreating Old Problems

The new system has clean architecture. It also has a product team with the same delivery pressure the old system had. It has a business with the same quarterly commitments. It has the same organizational habits, the same tendency to treat engineering estimates as negotiating positions, the same impulse to ship first and clean up later. The codebase is new. The conditions that shaped the old codebase are not.

You can rewrite PHP into Go and still preserve every terrible organizational decision.

The patterns that produced the legacy system were not produced by the language or the framework. They were produced by the way the organization makes decisions under pressure, and those habits do not reset because the codebase does. The first time a feature is needed urgently and the architectural boundary is in the way, the same conversation happens that happened in the old system: is it worth slowing down to do this right, or can we take a shortcut just this once. The answer tends to be the same as it always was, because the incentive structure is the same.

Greenfield projects have a well-documented tendency to become the thing they replaced within a few years, because they are built by the same organization, under the same pressures, for the same customers. The new billing module is clean today. It will handle an edge case next year that introduces its first kludge. The year after that, a different edge case. Five years from now, a new developer will open it, find something that should not be possible, and wonder how it got that way.

The rewrite buys time and clarity. It does not buy immunity from the forces that made the rewrite necessary.

Successful Rewrites

They happen. It is worth saying that directly, because this series has covered enough failure modes that the impression could form that the answer is simply to avoid everything described.

Successful rewrites share a set of characteristics that are less glamorous than the technical ones but more predictive of the outcome. The scope is bounded at the start and defended throughout. The migration is incremental, moving one domain or one workflow at a time rather than attempting to replace everything simultaneously. The strangler pattern, routing specific functionality to the new system while the old one handles everything else, turns the migration into a series of smaller decisions rather than one enormous one, which means it fails in smaller ways and is easier to validate at each step.

The domain model in the new system reflects genuine understanding of the business domain, not just the technical structure of the old one. This requires conversations with people outside engineering: the support team that knows which edge cases actually occur, the sales team that knows which customer commitments have architectural implications, the finance team that knows what the billing system is actually required to produce. These conversations are slow. They are also the work, and skipping them is how the new system ends up with the same undocumented complexity as the old one, just newer.

Business alignment throughout the migration is not optional. When the engineering team and the business are not looking at the same definition of done, the migration either stalls because engineering keeps finding more to do, or ships prematurely because the business stops waiting. The organizations that get this right treat the migration as a product, with incremental deliverables, explicit validation steps, and a shared understanding of what "complete" means for each phase.

The best rewrites are not attempts to erase history. They are attempts to finally understand it.

The legacy system, for all its accumulated ugliness, was shaped by real requirements, real customers, and real constraints. The rewrite that takes that history seriously, that asks why the billing module looks the way it does before deciding how the new one should look, that treats the old code as documentation of decisions rather than evidence of failure, tends to produce systems that age better than the ones written in reaction to the past.

Eventually the new platform stabilizes. The old one is decommissioned. The migration is declared complete.

And the new platform has its first production incident.

Which means it is time to start paying attention.

If your organization is planning a rewrite, platform migration, modernization effort, or major architectural transition, I help teams identify risks, understand legacy behavior, and build migration strategies that survive first contact with production.

Get Started

Recent Post

  • Phase 8: The Rewrite
    Phase 8: The Rewrite
    08 Jul, 2026
  • Phase 7: The Rebuild Conversation
    Phase 7: The Rebuild Conversation
    01 Jul, 2026
  • Phase 6: Stabilization Attempts
    Phase 6: Stabilization Attempts
    24 Jun, 2026

follow us

Logo

Backed by 20+ years of hands-on software development expertise, mithra62 (Eric Lamb) transforms ideas into powerful, reliable solutions designed to work exactly how you want, every time.

© Copyright 2026 | mithra62

Useful Links

  • About
  • Projects
  • Writing
  • Work
  • Request Quote

Services

  • Fractional CTO
  • Application Architecture
  • Platform Audits
  • Platform Rebuilds

Contact Info

Get in touch now to begin work immediately.

  • Email: eric@mithra62.com
  • Contact: 310.739.3322