Two services share a database because it was faster to build that way. Both teams now block each other on every schema change. What do you do?
Show the full answer Hide the answer
Name the actual coupling
They are not two services. They are one deployable unit split across two repositories, with the coupling moved from code — where a compiler would catch it — into a schema where nothing does.
That framing matters because it tells you the remedy is separation of data, not better coordination.
Establish who owns what
Go table by table: which service writes it. Three categories emerge.
Clearly owned by one — the majority, usually. These separate cleanly.
Written by both — the real problem. Usually one write is a shortcut where a call to the other service was avoided.
Read by both, written by one — separable by giving the reader an interface or a replica.
Separate incrementally
Start with the owned tables. Move them behind the owning service, replacing direct reads with an API call or an event subscription. Each move is independent and reversible.
For the shared-write tables, decide the owner, then convert the other side's writes into requests to the owner. This is the expensive part and it is where the business logic that was implicit in the schema becomes explicit.
For cross-service reads that need to be fast, a replicated read model kept current by events is usually the right answer, and it makes the staleness explicit rather than hidden.
The interim measure
Until separation is done, put a schema change process in place: a shared migration review, expand-contract on every change, and contract tests over the tables both touch. Unpleasant, and it stops the blocking while the separation proceeds.
What to tell the teams
The velocity they gained by sharing is now being repaid with interest. Separation is the price of the independent deployment they both want, and it is smaller now than it will be in a year.