The modernization program looked like a success. The monolith was split into services, each with its own repository and pipeline. Yet releases still need coordination, a schema change still breaks three teams, and nobody can say which service owns the customer record. You have built a distributed monolith.
It is one of the most common places for modernization to stall, and it happens for an understandable reason: splitting code is much easier than splitting data.
How to recognize it
- Several services read and write the same tables directly.
- A database migration requires a coordinated release across teams.
- Services call each other synchronously in long chains to complete one request.
- Reporting queries join across tables that belong to different services.
If two or more of these are true, the services are independent on paper only. You carry the operational cost of distribution without its benefits.
Step 1: Map who touches what
Before changing anything, build an access map: for each table, which services read it and which write it. Database query logs and code analysis together give an honest picture. This map usually reveals that a handful of core tables, often customers, orders or accounts, cause most of the coupling.
Step 2: Assign a single owner to each table
Every table needs exactly one owning service with the right to write to it. Other services stop writing directly and go through the owner's API instead. This is the most important change and the one that delivers most of the benefit, even before any data physically moves.
Step 3: Replace shared reads with contracts
Reads are harder to give up because direct queries are fast and convenient. Replace them gradually with one of three patterns: a synchronous API call when data must be current, an event stream that lets consumers keep their own copy, or a read model built for reporting. Choose per use case rather than mandating one pattern everywhere.
A service owns its data when no one else can change it without asking.
Step 4: Move the data last
Once access flows through owners and contracts, physically separating schemas or databases becomes a low-risk, mostly mechanical step. Teams that try to move data first usually end up rolling back, because hidden consumers break in production.
Keep the business running throughout
Each step is incremental and reversible. Put an API gateway in front of the system so routes can shift from the old path to the new one without clients noticing. Run old and new paths side by side, compare results, then cut over. Done this way, decomposition proceeds one boundary at a time with zero operational downtime.
The goal was never a count of microservices. It was teams that can change their part of the system without waiting on anyone else. Data ownership is how you get there.