skip to content

In a microservices architecture, why does each service typically own and manage its own database rather than multiple services sharing one database?

level: juniorimportance: must knowfreq 85%

answer

  1. own writer only
  2. integration database anti-pattern
  3. API/events not SQL
  4. bounded context boundary
  5. physical vs logical isolation

basics

~10 s

Each service gets its own database so only that service can change its data directly; others must ask through its API, which stops hidden dependencies and lets services evolve independently.

solid answer

~30 s

Database-per-service means each service is the sole writer (and often sole reader) of its own schema; other services access its data only through its published API or events, never direct SQL. This enforces the module boundary at the data layer, not just the code layer: a shared database lets any service silently depend on another's internal table structure, so a harmless-looking schema change becomes a cross-team outage. Owning your own database also lets each service pick the storage technology that fits its access pattern (SQL, document store, cache) and scale or deploy independently.

go deeper

for a junior

Can state that each service should own its data and explain in plain terms why sharing a database is risky (one team's change breaks another).

for a middle

Explains the API/event-only access rule, names the integration-database anti-pattern, and knows physical vs logical isolation is a spectrum.

for a senior

Discusses the operational trade-offs (duplicated infra, no cross-service transactions, harder reporting) and how their team enforces the boundary (schema-per-service DB, access reviews, service mesh policies).

for a principal

Weighs organizational factors — team topology, cost of dozens of DB instances, when to relax the rule for a small org — and designs the read-model/reporting strategy that avoids reintroducing coupling.

## What the rule actually requires **Database-per-service** means that each microservice is the exclusive owner of the data it needs to do its job: - it has its own schema (and often its own database instance or cluster); - it runs the migrations for that schema; - it is the only piece of code in the system allowed to write to those tables. Other services never open a connection to that database and never run SQL against it directly. If Service A needs data that Service B owns, A has exactly two legitimate paths: 1. call an API that B exposes, or 2. subscribe to events that B publishes when its data changes. ## Physical or purely logical The isolation can be physical or purely logical — what makes the pattern real is the access rule, not the hardware topology. | Shape | What it looks like | |---|---| | Physical | a separate database server or managed instance per service | | Purely logical | separate schemas and database users within one shared instance, with no cross-schema grants | A three-service startup running all three schemas on one Postgres instance with per-service roles and zero cross-schema queries is following the pattern just as much as a large company running fifty separate database clusters. ## Why the rule exists The reason this rule exists is that a shared database, used by multiple services as their integration mechanism, silently reintroduces every coupling problem microservices are meant to remove. This is sometimes called the **'integration database' anti-pattern**: when two services both read and write the same tables, each one is implicitly depending on the other's internal representation of its data — column names, types, foreign keys, even row-level invariants that only make sense from inside the owning service's business logic. That dependency isn't visible in any interface or contract; it's just there in a query somewhere. A schema change that looks perfectly safe from inside the owning team's context (renaming a column, splitting a table, adding a `NOT NULL` constraint) becomes a cross-team incident because some other service was quietly relying on the old shape. Database-per-service forces that dependency to surface as an explicit, versioned interface — an API endpoint or an event schema — that the owning team can evolve deliberately and the consuming team can adapt to on their own schedule. ## The trade-off The trade-off is real and shouldn't be hand-waved away. On the benefit side: - each service can deploy independently without coordinating schema migrations with anyone else; - it can choose the storage technology that actually fits its access pattern (a relational store for an orders ledger, a document store for a flexible catalog, a graph store for a recommendation engine); - failures in one service's database don't directly corrupt or lock another's. On the cost side: - you lose the ability to run a single SQL join across two services' data; - you lose the ability to wrap a change to two services' data in one local ACID transaction; - you now operate and monitor N databases instead of one; - any cross-service view of the data has to be built and kept consistent asynchronously, which means living with eventual consistency instead of always-fresh reads. ## How it fails in production In production, this rule fails in a specific, recurring way: someone finds it inconvenient. A report is due, a dashboard needs a number, a service needs 'just one more field' from another service's table, and instead of waiting for an API to be built, an engineer opens a read-only connection straight into the other service's production schema. It works, right up until the owning team refactors that table during an unrelated change and breaks a consumer they didn't know existed and never agreed to support. The same failure shows up in more subtle forms: - a **'read replica'** of another service's database that starts as a reporting convenience and slowly becomes a load-bearing dependency for live decisions; - a service that avoids direct SQL but instead makes a synchronous API call to another service inside a loop for every item in a list — not a database coupling anymore, but it recreates the same availability coupling under a different name, a **'distributed monolith'** where every service's uptime depends on every other service it talks to. ## Putting it together A concrete illustration: in an order-management system, an Order service should never run a query against the Inventory service's stock table to check availability. Instead it calls Inventory's `checkAvailability` API (or, for less time-critical checks, consumes an event stream Inventory publishes about stock levels). When the Inventory team later decides to split their single stock table into a 'reserved' and 'available' quantity for a new feature, that's an internal detail; the Order service's contract (the API or event shape) doesn't have to change, and Order keeps working without anyone from that team needing to notify or coordinate with the Order team at all — which is the entire point of owning your own data.

  • Does database-per-service require a physically separate database server for every service?
    No — logical isolation (separate schema/credentials with no cross-schema queries) is often enough, especially in smaller organizations or on managed platforms where spinning up dozens of database instances is costly. What matters is that only the owning service has write access and no other service depends on its internal table structure. Physical separation adds independent scaling and failure isolation but isn't the defining property of the pattern.
  • What is the 'integration database' anti-pattern this rule is defending against?
    It's when multiple services or applications read and write the same shared schema directly, using the database itself as the integration mechanism instead of APIs. It looks convenient short-term because it avoids building an API, but it means any service can depend on any other's internal representation, so schema changes become multi-team coordination events and true independent deployability is lost.
  • How do you handle a report that needs data from five different services' databases if you can't join across them?
    You typically build a read-only aggregation layer — either a service that calls the other services' APIs and joins in memory, or, more commonly at scale, a data warehouse or read-model fed by each service's change events via CDC or an event stream. This trades real-time consistency for an eventually-consistent, query-optimized copy that doesn't require touching the owning services' live databases.

Like separate personal bank accounts: you manage your own account and can only affect someone else's balance by making an authorized transfer request (a wire transfer/API call), never by reaching into their ledger and editing it directly.

saying these in an interview costs you the question

  • Suggests giving a reporting job direct SQL access to another team's production tables
  • Doesn't distinguish physical vs logical isolation, insists every service needs its own database server
  • Thinks the rule is only about scaling, misses the coupling/ownership argument
  • Proposes cross-service joins as the default way to fetch related data
  • Can't explain how another service should then get that data (no mention of APIs/events)

context