The legacy codebase still runs the business. It is not small. It is six vertical functions deployed as separate services, sharing a data layer and a code tree, so “service” is a deployment unit here, not a boundary. It reads like a place rather than an architecture – rooms we know the shortcuts of, walls not quite where a greenfield build would put them. It has been running for years. It works.
Everyone agrees on Go as the way forward. The team has agreed. Tech leadership has agreed. What has not been agreed to is when.
Scout
We pick the candidate service first. The scouting matters more than the writing later, because a bad choice means a carve-out that cannot actually separate without pulling the codebase apart. We look for functions already near the edges – fewer cross-function queries, a shape that does not mutate the main data tree, behavior that downstream services depend on through a narrow interface.
The user service came up first. The original was handwritten, its own thing, years old, with an auth model the current world has outgrown. OAuth2 compliance is due anyway. The new service – user and accounts, separated clean, Go, its own data – can be written without touching any of the other five functions. A scout’s dream.
Plan
Planning a carve-out under coexistence is different from planning one during a sanctioned rewrite. The new service must keep the old path alive. Both read from the shared data for a while. Writes dual-route. The old endpoints stay answering until the last caller migrates. We write the checklist with the old service as the compatibility surface, the new service as the shape we want, and the migration as a slow traverse from one to the other.
We keep the legacy service running because the team is lean and business continuity is non-negotiable. The old code has shortcuts that make sense if you know them. Mixed concerns, tailored boundaries, conveniences that read like mistakes if you came in yesterday. Those who know, know. We honor that. The carve-out removes what can be removed without disturbing what should not be.
Wait
The new user and accounts service is ready. It has been ready for weeks. The old one still runs.
What gets in the way is everything. A product feature arrives with a quarter’s roadmap attached. A regulatory change takes a sprint. A customer incident wants its own two weeks. The carve-out is ready but the carve-out does not have a quarter. “Next quarter” becomes a thing we say. Every quarter is next quarter.
This is the work the diagrams do not show. A migration plan fits on a slide. A migration fits in whatever gap the product roadmap leaves open, which is not much. We scout, we plan, we wait.
What grows
Between the quarters where nothing migrates, the Go surface grows. New services get built new, in Go, with their own data. The legacy codebase stops being the place where new functionality lands, even when it remains the place where existing functionality lives. The split happens not at a migration moment but at every new-service decision.
Over time, the ratio shifts. Go surface grows. Legacy surface shrinks where carve-outs ship. Neither happens fast. Both keep shipping.
The best migrations are the ones nobody announces. There is no kickoff, no launch event, no slide with “completed” on it. There is a quarter when the user-and-accounts service finally replaces the old user path. There is another quarter when a second function follows. There is a retrospective someone writes years later about how the legacy codebase stopped being where the business ran, without a single moment anyone can point to.
We are not at that retrospective yet. We are at scout, plan, wait.
