Most rewrites don't die on architecture. They die because the old system's accidental behavior had already become somebody else's contract.
Inside the boundary I own the code. Outside it I own the behavior, including the parts nobody designed, documented or meant to promise. A rewrite can be clean on the inside and still breaking on the outside, and that is where money and compliance live.
So before rewriting I ask five questions, and none of them are about the stack.
-
Who consumes my callbacks, and what do they assume about them? Payment webhooks get delivered twice, out of order, with fields that are sometimes absent. Every integrator has quietly coded around whatever the old service happened to do.
-
What is anyone branching on that isn't part of the API? Error codes, HTTP statuses, message strings. A cleaner error taxonomy is a breaking change for everybody with an if-statement on the old one.
-
Which identifiers already escaped? Payment references and requisites end up in bank statements, invoices and partner files. Change their shape and attribution stops matching things you can no longer re-issue.
-
What exports feed finance and compliance? Reconciliation, audit pulls, scheduled reports. A renamed column is a month-end incident, not a refactor.
-
Who can't be force-updated? Shipped mobile builds and partner integrations decide how long the old behavior has to stay alive, and that number sets the real timeline, not the rewrite.
My take: treat a rewrite as additive at the boundary first. Keep the old behavior as an explicit compatibility surface, run both against real traffic until parity gets boring, then retire the old path deliberately instead of meeting its consumers in production.
I wrote more about where these seams actually tear here: https://polycratia.com/c/3b4a8764
If you've done a big rewrite, I'd like to hear which external dependency surprised you most: a webhook consumer, a report, or an identifier you took for internal...