Database Per Service
Each microservice owns its private, dedicated database — no other service can access it directly. This pattern enables independent deployment, polyglot persistence, and fault isolation at the cost of cross-service query complexity.
What it is
Database per service is the microservices pattern where each service has exclusive ownership of its database — no other service may access that database's tables or collections directly. Data is exposed only through the service's API. This means the order service has its own database for orders, the inventory service has its own database for stock levels, and the customer service has its own database for customer profiles. If the order service needs a customer's name, it calls the customer service API — it does not query the customer database directly.
The "database" in this pattern can mean a separate server, a separate schema within a shared server, or (with less isolation) a separate table set behind access controls. Full server isolation is the most complete form; schema-per-service is a common pragmatic compromise that maintains logical isolation without the cost of running a server per service.
Why it exists
Shared databases are the most common source of coupling in microservices architectures. When multiple services access the same tables, a schema migration in one service can break others; a slow query from one service can degrade response times for all; and deploying one service may require coordinating schema changes with teams owning other services. Database per service enforces the encapsulation boundary at the persistence layer — a service's internal schema is an implementation detail that can change without any coordination with consumers of its API.
When to use
- When microservices must be deployed independently — a shared database makes independent deployment impossible because schema changes must be coordinated across all services.
- When services are owned by separate teams — teams should not need to coordinate schema changes with other teams' release cycles.
- When different services have different scalability requirements — the catalog service might need horizontal sharding while the user service remains a single PostgreSQL instance.
- When polyglot persistence is desired — giving each service its own database is a prerequisite for each service choosing the best database technology for its domain.
- When you need fault isolation — a crashed database instance should only bring down the service that owns it, not all services sharing it.
When not to use
- Don't apply this pattern to a monolith — the pattern's benefits apply specifically to independently deployable services; in a monolith, multiple schemas add overhead without the deployment independence benefit.
- Don't apply it if service boundaries are not yet stable — premature service decomposition with database-per-service creates a complex distributed system before the team has validated the domain model.
- Don't use it if your organization lacks the operational maturity to manage many database instances — each additional database adds backup, monitoring, and on-call complexity.
- Don't use it as an excuse to avoid fixing a reporting problem properly — if the motivation is "we need cross-service reporting", the solution is a data warehouse and ETL, not a relaxation of the ownership rule.
Typical architecture
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Order Service │ │Inventory Service │ │ Customer Service │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │
│ │ Orders DB │ │ │ │ Inventory DB │ │ │ │ Customer DB │ │
│ │ PostgreSQL │ │ │ │ MongoDB │ │ │ │ PostgreSQL │ │
│ └─────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │
└────────┬────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
└───────────────────┴──────────────────────┘
▲ No direct DB access between services
▲ Communication only via API or domain events
Cross-service query (reporting):
┌───────────────────────────────────────────────────────┐
│ Reporting DB (read-only, updated via ETL / CDC) │
│ All services emit events → Kafka → Warehouse (Redshift)│
└───────────────────────────────────────────────────────┘
Pros and cons
Pros
- Independent deployment: schema changes in one service don't require coordinating with other teams or causing downtime for unrelated services.
- Technology choice: each service selects the database that best fits its access patterns (polyglot persistence).
- Fault isolation: a database outage is scoped to a single service; other services degrade gracefully rather than failing entirely.
- Independent scaling: the catalog service database can be scaled to handle 100k reads/sec without affecting the order service database.
- Encapsulation: the internal schema is a private implementation detail; services communicate through stable versioned APIs.
Cons
- Cross-service queries require either API composition (N+1 calls problem) or maintaining a separate reporting store fed by events.
- No distributed transactions: business operations spanning multiple services must use sagas with compensating transactions — eventual consistency, not ACID.
- Data duplication: the same entity (e.g., a product) may need to be partially replicated into multiple service databases, requiring synchronization.
- Operational overhead: multiple database servers, each requiring its own backup strategy, monitoring, patching, and capacity planning.
- Referential integrity lost: foreign key constraints across service boundaries are no longer enforceable by the database; integrity must be maintained at the application level.
Implementation notes
For cross-service reporting and analytics, publish domain events to a message broker (Kafka) and consume them into a data warehouse (Redshift, BigQuery, Snowflake). Each service publishes events when data changes; the warehouse maintains denormalized views of all domains. This is the correct solution for cross-service analytics and avoids the temptation to query multiple service databases directly. Alternatively, use CQRS with a dedicated read service: the read service subscribes to events from multiple services and materializes a join view as a read model (e.g., an order summary view joining order, customer, and product data).
For cross-service transactions (e.g., place an order, deduct inventory, charge payment), implement the Saga pattern. The orchestrator saga sends a command to each service and handles compensation if any step fails: if the payment service rejects the charge after inventory has been deducted, a "release inventory" compensating command is sent. Sagas accept eventual consistency — there's a window where the system is in a partially consistent state. Design business processes to handle this (e.g., show "order pending" rather than "order confirmed" until all saga steps complete).
Common failure modes
- Shared database re-emerging: Teams find the API-composition overhead inconvenient and start allowing "read-only" direct database access from other services; over time the shared database coupling problem is fully restored.
- Missing saga compensation: An order saga reserves inventory and then the payment step fails; the inventory reservation is never released; stock levels show items as reserved that will never be purchased.
- N+1 query problem in API composition: To render a list of 100 orders with customer details, the order service calls the customer service API 100 times (one per order); the API becomes slow and the customer service is overwhelmed.
- Event schema drift: The order service changes the format of its order-placed event; the reporting service that consumes the event breaks silently; the reporting database stops updating.
- Referential integrity violation: A customer is deleted from the customer service; the order service still has orders with that customer ID; there's no foreign key constraint to prevent this; customer-related order queries return null references.
Decision checklist
- Are service boundaries stable and validated — premature database-per-service with shifting service boundaries is extremely expensive to refactor?
- Is there a strategy for cross-service reporting (data warehouse + event streaming) so teams don't bypass the pattern under reporting pressure?
- Are saga compensation actions defined for every multi-service business transaction that can partially fail?
- Is API composition designed to avoid N+1 problems (batch fetch endpoints, GraphQL DataLoader, or denormalized read models)?
- Is there a schema registry or event contract system to detect breaking changes in domain events between services?
- Is the team operationally prepared to manage multiple database instances: backup, monitoring alerts, and on-call runbooks for each?
Example use cases
- E-commerce microservices: Order service (PostgreSQL), inventory service (Redis + PostgreSQL), catalog service (MongoDB), payment service (PostgreSQL with strict ACID), notification service (DynamoDB for notification history). Each team deploys independently without coordination.
- SaaS multi-tenant platform: Tenant management service (PostgreSQL), billing service (PostgreSQL with Stripe integration), analytics service (ClickHouse for usage metrics), user management service (PostgreSQL + Redis for sessions). Services share no database; tenant context is propagated via JWT claims in API calls.
- Booking platform: Availability service (Redis for real-time counts + PostgreSQL for source-of-truth), reservation service (PostgreSQL), pricing service (PostgreSQL + Redis cache), review service (MongoDB). A booking saga spans availability, reservation, and payment services with compensation on failure.
Related patterns
- Polyglot Persistence — Database per service enables each service to choose the best database technology for its domain.
- Saga Pattern — The mechanism for managing distributed transactions across services that each own their databases.
- CQRS — A common companion pattern: write model in the service's private DB; read models materialized from events into a queryable store.
- Transactional Outbox — Ensures reliable event publishing from a service's private database without distributed transaction coordination.