Overview
Database per service enforces the boundary at the storage layer. If another team can query your tables, your schema is a public API whether you meant it or not.
How it works
Services share data only through contracts.
Order Service --> orders_db
Payment Service --> payments_db
Customer Service-> customers_db
Order needs customer name:
option A: call Customer API (runtime coupling)
option B: subscribe to CustomerUpdated events,
keep a local read copy (autonomy, eventual)
Logical separation can be enough at first: separate schemas with separate credentials in one cluster, split into clusters when scale demands.
Data needed by several services follows one of a few patterns. API composition has a gateway call several services and merge results. A local read model keeps a copy of the other service data, updated from events with an outbox for reliable publishing. A shared reference dataset, like country codes, can be replicated as a library or read-only table. Choose per case by how fresh the data must be and how bad it is when the owning service is down.
Pitfalls
- Sharing one database with per-team tables and calling it microservices.
- Chatty synchronous calls to fetch data, creating a distributed monolith.
- Trying to emulate joins in application code on hot paths.
- Splitting too early before boundaries are understood.
Key takeaways
- Own your data; expose it through APIs or events only.
- Use sagas and events instead of distributed transactions.
- Replicate needed data locally and accept eventual consistency.
- Start with logical separation and split physically when needed.