Skip to main content

By General Applications · · 6 min read

Legacy System Modernization: What Moves, What Stays, What Stops

Software ArchitectureLegacy ModernizationPlatform Reliability

Modernizing a legacy system means deciding what to replace, what to keep, and what to remove. Moving the code is only part of that work. The replacement also has to support the users, data, integrations, and scheduled tasks that depend on the old system.

A migration plan should make those decisions explicit. Account for the existing files, test the functions you intend to preserve, and decide how the new system will take over. Without that accounting, “finished” can mean the new code is written while the old system is still needed.

Choose the Method Around the Constraint

Start with the reason for changing the system. An unsupported runtime creates a deadline. A slow report creates a performance problem. A deployment that requires someone to edit production files creates an operational problem. Each may call for a different amount of replacement.

Migration methods and the conditions they require.
MethodUseful whenWhat you need to resolve
StranglerA capability can move while the rest of the system keeps running.How calls reach the right implementation, and how both systems access data.
RebuildThe required scope is bounded and the whole cutover can be rehearsed.How to discover missing behavior and keep up with changes to the old system.
HybridSome components need replacement while others can remain.Where each boundary sits and who maintains what remains.

With a strangler migration, you replace capabilities gradually. Order history might move first, with order creation still handled by the old application. That requires a routing boundary and a period of coexistence. Inside one application, branch by abstraction provides a related technique: callers use an interface while you replace the implementation behind it.

A rebuild concentrates the transition into a larger cutover. It can be reasonable for a small application whose inputs, outputs, users, and integrations are known. The assessment has to include more than screens: scheduled jobs, permissions, exports, and exception handling can all add scope. Changes shipped to the old application during the rebuild must also be assessed against the replacement.

A hybrid lets you keep a working pricing library, replace the UI, and move batch jobs separately. The retained library still needs an owner and a supported runtime. Keeping a component is a decision with maintenance consequences.

Before choosing any of these, check whether a narrower change would solve the problem. Replacing the whole application needs a reason beyond the age of its code.

Account for the Source

A migration ledger is a record of what will happen to the old system's artifacts. For a repository, take a baseline at a named commit and account for every tracked file: source, tests, schema, migrations, configuration templates, deployment definitions, and build scripts. Group generated output under its generator where that makes sense, but make the grouping explicit.

The repository is only part of the inventory. Add database procedures, externally scheduled jobs, reports, and manual steps found in the running environment. A workbook has sheets, formulas, macros, and exports to account for. An undocumented Friday correction needs an entry even if it has no file.

Give each item a decision. Leave it marked as unresolved until someone has made that decision.

Decisions recorded in the migration ledger.
DecisionWhat happens to the item
ReplaceBuild a new implementation and record where it lives.
AdaptModify the existing implementation for the new system.
RetainKeep it running and assign responsibility for maintaining it.
RetireRemove it, with a reason and approval from whoever depends on it.

Keep the ledger beside the migration code and review changes with the implementation. Use stable item IDs so renaming a source file does not erase its history. Record the source revision assessed, destinations where applicable, an owner, and links to verification evidence. A retirement record needs a reason and approval; it does not need a fictional replacement path.

CI can enforce the repository part of this agreement. Compare the current legacy tree with the assessed inventory. Added files need a decision, changed files need reassessment, and deleted files need an accounted-for outcome. Keep the ledger and replacement code outside that source comparison. Otherwise the bookkeeping itself becomes migration scope.

These checks expose drift. They cannot find work that was never recorded in the repository. That requires inspecting the environment and talking to the people who use it.

Test the Replacement

A file marked as migrated tells you where the code went. It does not tell you whether the feature works.

For each feature you are keeping, define what it needs to do and test it in the new system. Run the old and new implementations with the same inputs and compare the results. Check permissions, error handling, and connections to other systems as well as the normal successful operation.

Some behavior should change during migration. If you are fixing a bug or removing a feature, record the expected change and get approval from the person responsible for that part of the system.

Track implementation, testing, and actual use separately. Code can be written without being tested, and tested without being deployed. The ledger should show which of those steps is complete.

Decide Who Writes During the Transition

If the old and new applications can both update the same records, they can overwrite each other's changes. Decide which application is allowed to write each type of data during the migration.

The other application can read that data or send updates through the application responsible for it. If you copy data between databases, define which direction updates travel and check that they arrive. Two databases containing the same records can drift apart as soon as either one changes.

You can move a feature without moving its data at the same time. The new implementation can continue to use the existing database until you are ready to migrate it. That gives you two smaller changes to test and deploy.

Rehearse Cutover and Retirement

Rollback depends on what happens after the switch. Once the new application has accepted orders, routing users back to the old one is safe only if it can see and correctly process those orders.

Choose the recovery path before cutover. You might preserve a compatible data format for a limited window, build and test a reverse conversion, or commit to repairing the new system in place after a defined point. State what triggers recovery, who makes the call, and how writes are handled while it happens. Rehearse with data created after the switch, not just a copy taken before it.

Retirement has its own work. Confirm that callers have moved, scheduled jobs are disabled, access is removed, and required records remain available under the retention policy. Remove temporary adapters when their consumers are gone. If an old reporting database must remain until year-end, name it as retained infrastructure with an owner and an end date.

For a workbook replacement, revoke ordinary write access before archiving it. Otherwise the next missing feature can send a team back to the spreadsheet, leaving two competing records of the same work.

Give Coding Agents Bounded Migration Work

An agent can help inventory files, trace callers, draft tests, and translate an implementation. Give it a source revision, a defined scope, and checks written around the required behavior. Review its proposed dispositions against the running system.

Keep implementation and acceptance separate. Tests based only on the generated code can repeat the same mistakes as that code. Use independently reviewed fixtures and workflow checks; let the ledger link to their results. This is one application of deterministic controls around AI agents.

For a first migration slice, choose a capability small enough to follow from input to operational result. Inventory its artifacts, verify its behavior, assign its data writer, and rehearse its switch. Then use that slice to judge whether the chosen method is workable before committing the rest of the system to it.

Related Reads

  • RAID Is a Failure Budget, Not a Backup Strategy

    RAID is part of a reliable-storage design, not the design itself. The useful questions are what can fail, how the workload behaves, what performance matters, and how recovery will work when a disk eventually needs replacement.

    · 6 min read

    Read Article
  • Deterministic Controls Around Nondeterministic Models

    Large Language Models are probabilistic by design, making them unstable foundations for business logic. We outline the architectural patterns required to wrap nondeterministic AI models in reliable, deterministic controls.

    · 5 min read

    Read Article

Have thoughts on this post?

We welcome discussions and feedback on our architectural observations.

Start a Conversation