Database per service and the shared database
Why each service should own its data, what a shared database costs, and the coping patterns while you get there.
key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral
Split the data along the same lines
A service boundary is only real if it owns its data. The Decomposing the Database chapter of Monolith to Microservices shows how to prepare the data before any service exists.
- Repository per bounded context: give each context its own data-access code. It shows which tables each part really uses. A tool such as SchemaSpy can also show foreign keys between them.
- Database per bounded context: give each context its own schema while it is still one deployable. The book suggests this for brand-new systems, where the domain is not stable enough to commit to services.
- Logical versus physical: separate schemas can run on one database engine. That is simpler but is one point of failure.
Lesson 13 covers what happens next, when a service must take its tables and the monolith still needs them.
The shared database and why it hurts
Sharing one database between services is the most common form of implementation coupling. The core problem: you no longer decide what is shared and what is hidden, so nobody knows which parts of the schema are safe to change.
Cohesion suffers too. When three services can change an order, the rules for its state machine are spread out, and every rule change must be repeated in each service. (Here "database" means a logically isolated schema; one engine can host many.)
| Problem | What it means |
|---|---|
| Hidden consumers | You know others read the schema but not which tables or columns they use. |
| Unclear control | Business logic that changes the data lives in several places. |
| Schema as public contract | Even unmaintained clients freeze your table design. |
When sharing is acceptable
| Case | Why it is safe enough |
|---|---|
| Read-only static reference data | Country codes, postal lookups: very stable, changed as an admin task. |
| A database exposed on purpose as an endpoint | Designed and managed for many consumers (see the database-as-a-service section). |
Everything else should move toward each service owning its data. Where that cannot happen yet, the coping patterns below stop things getting worse. This builds on split the data along the same lines above and living with a shared database below.
From Monolith to Microservices, ch. 4: "Pattern: The Shared Database" and "Schemas and Databases".
Living with a shared database
Sharing a database means you cannot tell what is shared or hidden, and no one controls the business rules. The Decomposing the Database chapter lists coping patterns to use while the real split is not yet possible.
| Pattern | Idea | Limit |
|---|---|---|
| Database view | Give clients a read-only projection of the schema | Read-only, usually the same database engine, and someone must keep it in step with the schema |
| Wrapping service | Hide a schema that is too tangled behind a thin service and stop it growing | Callers must change to API calls. It is a bandage |
| Database as a service (reporting database) | Fill a separate read-only database from the internal one with a mapping engine, ideally change data capture | Data lags. More work, but it can use a different database technology |
| Aggregate exposing monolith | The monolith offers an API for the data a new service needs. The same idea is called monolith as data access layer | Works while the monolith still owns the state changes |
The book tells of a bank trading system where more than 20 unknown applications shared one login. The schema had become a public contract that could not change. Give each client its own credentials from the start. A secret store can make that practical.
Coping pattern: view and wrapping service
Two ways to regain control of what is shared without splitting the data yet.
Database view
A view is a limited projection of the underlying schema. It hides columns and tables, and lets the owner change tables as long as the view keeps working.
| Aspect | Detail |
|---|---|
| Limits | Usually read-only; the view and source often must sit on the same engine; support varies by database. |
| Materialized view | Precomputed, so reads are cheaper but may be stale. |
| Ownership | Treat a published view like a service interface, kept current by the team owning the source schema. |
| Use when | Full decomposition is impractical now. Prefer real schema decomposition if the goal is a service interface. |
Database wrapping service
When the schema is too tangled to touch, hide it behind a thin service. In the book, a bank kept its entitlements logic, tangled in stored procedures, behind a new service so other teams stopped adding data and behaviour to it.
| View | Wrapping service | |
|---|---|---|
| Shape of data | Must map onto existing tables | Any projection you can code |
| Writes | Typically no | Yes, through API calls |
| Consumer change | Point at another schema | Switch from DB access to API calls |
| Best with | Read-only consumers | Same team owns schema and wrapper |
From Monolith to Microservices, ch. 4: "Pattern: Database View" and "Pattern: Database Wrapping Service".
Database-as-a-service interface (reporting database)
Sometimes clients just need a SQL endpoint: large reads, or tools such as BI dashboards. Do not hand them your working database. Build a separate, read-only one and fill it from the internal one.
Mapping engine options: change data capture is the most robust and freshest (for example Debezium); listening to service events works if you already emit them; a batch copy lags longer, is hard to scope, and can fail silently. The author now prefers CDC.
| Compared to a view | Detail |
|---|---|
| Technology | The exposed DB can be a different engine (Cassandra inside, SQL outside). |
| Cost | More work, but more freedom to evolve the internal schema. |
| Path | Start with a view; move to a dedicated reporting database later if needed. |
| Fits | Read-only clients, large joins over one service data. Can feed a larger warehouse. |
The name differs from Fowler's reporting database pattern because reporting is only one use; the same idea serves any ad hoc query. See lesson 11 for CQRS views, and feeding the reporting database below. Do not underestimate the work of keeping the projection current.
From Monolith to Microservices, ch. 4: "Pattern: Database-as-a-Service Interface".
Read-only databases for others
Sometimes a consumer just needs a database to query: ad hoc SQL, large joins, or a BI tool that expects a SQL endpoint. You can offer this without exposing your internal tables. Three techniques, from lightest to heaviest:
| Technique | What it is | Limits |
|---|---|---|
| Database view | A read-only projection of a schema. A materialized view is precomputed | Same database engine; materialized views can be stale; tied to the schema owner |
| Wrapping service | A service in front of the schema that can build richer projections and take writes | Consumers must switch from SQL to API calls |
| Reporting database | A separate database, filled from the internal one, exposed read-only | You must keep it up to date; it lags the source |
Two points matter. Expose only the minimum, so the reporting schema can be shaped for its readers, even on a different database technology. And treat it as part of the service interface: when the internals change, the mapping changes, not the consumers.
From Building Microservices (ch. 3, Reporting Database) and Decomposing the Database (ch. 4).
Feeding the reporting database
The mapping engine copies changes from the internal database to the external one. It always lags a little, so readers see slightly old data.
From Decomposing the Database (ch. 4, Database-as-a-Service Interface) and Building Microservices (ch. 3).
Data lakes and warehouses
To query across many services, data can be pushed into one shared store. This is "communication through common data": one side writes, others read later, usually by polling. The two best-known forms sit at opposite ends of a coupling scale.
| Data lake | Data warehouse | |
|---|---|---|
| Who knows the format | The readers | The writers |
| If the structure changes | Readers adapt | Writers break unless updated |
| Good for | Large volumes, not low latency | Large volumes and structured reports |
From Building Microservices (ch. 4, Communication Through Common Data).
Recap
- A shared database hides nothing; split it so each service owns its data. Sharing is acceptable for static reference data and purposely exposed endpoints.
- Views and wrapping services control access; a dedicated read-only database serves ad hoc and report queries.
- Do not leave a shared database behind. Use views, wrappers, read-only copies or a monolith API as stepping stones.
- A reporting database is a read-only copy with its own schema, filled by a mapping engine (CDC, events or batch). Treat it as part of the service interface.
- A data lake takes raw data and readers parse it. A warehouse is structured and writers must know the schema. Keep the flow one-way.
Quick check
Show answer
Nothing is hidden: any schema change can break the other service, and the rules for a customer live in two places. Give the table one owner, then let the other service read through a view, a wrapping service or the owner's API until it has its own data.