Migration with parity proved
A Node.js application became Rails 7 at a consumer credit fintech, without stopping operations. For each API, the new version only took over after answering the same as the old one in production.
The context
A financial system ran on Node.js. The company decided to build a more robust Rails application to control the contract management integrations inside the bank's ERPs. They are the same integrations that credit origination and servicing depend on.
The constraint
Stopping was not an option. In consumer credit, every request that goes out wrong is a real customer's money.
The work was in guaranteeing request parity between the legacy and the new version, one integration at a time, with the business running.
The approach
Strangler fig with shadow traffic. Both systems run in parallel and every production request goes through a switch that decides who answers. The request also runs on the other side as a replay, so the responses can be compared. Once parity is confirmed on an API, the switch flips to the new system. API by API, until nothing is left on the legacy side.
who actually answers
Legacy
New
Why not all at once
A single cutover looks faster and is where I have seen the most projects break. The divergence shows up in production, with customer money in the middle, and the rollback costs more than the whole migration.
Migrating by module is everyone's second attempt. A module is a boundary on a diagram. The real dependencies do not respect it.
I cut by behavior. Every request runs on both sides and the responses are compared. The key flips when they stop diverging, and not before.
Where this paid off less than I expected: a good share of the divergences the replay found were data format differences between the two languages, not business rule errors. For that kind, a QA with a well-written product requirement gets there sooner and costs less than building the comparison. Replay pays for itself when the risk is the rule, not the serialization.
The result
Each API started being served by the new system after the replay showed a response identical to the legacy one. Operations did not stop at any point of the transition.