What is the Database per Service pattern?
Every service has a private data store, which can be a separate schema, database or instance, that no other service reads or writes directly. Data that others need is exposed through an API, or copied into their own store from published events.
What problem does the Database per Service pattern solve?
A shared database couples services through the schema. One column change breaks several services, deployments must be coordinated, and nobody can choose the storage that fits their workload. Private data is what makes independent deployment real.
How do you implement the Database per Service pattern?
- Assign every table to exactly one owning service.
- Remove cross-service joins and foreign keys. Replace them with IDs and API calls or local copies.
- Expose data through APIs for queries and through events for changes.
- Keep read-only copies of data a service needs often, updated from the owner's events.
- Enforce ownership with separate credentials and network rules, not just convention.
When should you use it?
- Services must deploy and scale independently.
- Different services have different storage needs, such as relational, document or search.
When should you avoid it?
- A small system where one team owns everything and one database is perfectly manageable.
- Operations that truly require ACID guarantees across all the data involved. Keep that data in one service.
What do you gain?
- Loose coupling at the data level
- Storage chosen per service
- Independent scaling and failure isolation
What does it cost?
- No cross-service joins or ACID transactions
- Some data is duplicated
- You need sagas and read models to replace what the database used to do
What are common mistakes with the Database per Service pattern?
- Separate schemas in one database with shared credentials. It is still a shared database.
- Reporting jobs that read other services' tables directly.
- Rebuilding joins in application code with dozens of API calls per request.
Database per Service in practice
Quayside Music
Twenty services connected to one MySQL cluster. A team shipped a query without an index, it locked rows other services needed, and streaming went down across the product.
Each service moved to its own database, and other services can only reach that data through its API or events. Cross-service reports moved to a read model fed by events.
Example decision record
- Context
- Services reading and writing each other's tables cause lock contention and block independent deployment.
- Decision
- Every service owns its data. No service queries another service's database directly.
- Consequences
- Schema changes no longer need cross-team coordination.
- Queries that span services need API composition or a CQRS read model.