A team strangling a monolith routes traffic by endpoint - new service handles GET /orders/{id}, monolith handles everything else. Both write to the same orders table. What is the problem with routing by endpoint?
Show the full answer Hide the answer
The mechanism
A strangler migration is about moving ownership of data, and an endpoint is not a unit of ownership. With reads in the new service and writes in the monolith, both systems share one table, and shared tables bring the problems that service boundaries exist to prevent:
- Neither side can change the schema, because the other reads it. The migration's main purpose - freeing the new service to evolve its model - is lost on day one.
- Caching in the new service is immediately wrong, since writes it never sees invalidate its data.
- Business rules exist in two places and drift. The monolith's write path validates; the new service's read path formats; the first new rule has to be written twice.
- The cutover never arrives. There is no moment at which this endpoint's data is fully owned by the new service, so the monolith can never give up that table.
Routing by entity instead - every read and write for order 4711, or for tenant B, or for the UK region, goes to one system - means one system owns that data completely, holds the rules, can change the schema, and can be cut over and verified independently.
What entity routing requires
A routing decision taken from the request's key before any handler runs (a gateway rule, a header or a lookup), a migration unit small enough to verify - a tenant, a region, a hash range - and acceptance that for a period two systems own disjoint subsets of the same concept, which means cross-entity queries and reports read from both. That reporting problem is the real price, and it is paid once rather than for ever.
Why the other options fail
- "It doubles deployments." Any strangler migration runs two systems; that is the premise, not a consequence of endpoint routing.
- "Reverse proxies cannot express endpoint routing." They express it trivially - path routing is the most basic rule there is, which is exactly why this design is so often chosen.
- "It prevents a different language." Nothing about path routing constrains the language. A shared table constrains the schema, which is the actual coupling.
When not to route by entity
When the endpoint maps to data nothing else touches. A reporting endpoint reading a replica, a new feature with its own table, a stateless computation, or a read-only view fed by change data capture: there is no write path to split, so the objection disappears. The test is one question - after this move, which system owns the write path for this data? If the answer is "both", the boundary is in the wrong place.
Entity routing has its own price, so prefer the simpler thing when you can. It needs a routing key available before any handler runs, a migration unit you can verify, and a reporting path that reads both systems for the duration. The pattern dates from Martin Fowler's strangler fig note (2004) and the practical rule has not changed: move 1 tenant or 1 region first, verify it, then widen - and if the whole dataset can be moved in a single release with 30 minutes of downtime, skip the strangler entirely and take the outage.