The database per service pattern is a fundamental principle of microservices architecture. Each service owns its data and exposes it only through its API. No other service can access a service's database directly. This article explores the pattern, its benefits, challenges, and practical implementation strategies.
The Core Principle
In a monolithic application, a single database serves all modules. This creates tight coupling between modules -- changing one module's schema can break another module's queries. The database becomes a bottleneck and a single point of failure.
With database per service:
[Order Service] -> [Order DB] (private)
[Payment Service] -> [Payment DB] (private)
[Inventory Service]-> [Inventory DB] (private)
Each service has complete ownership of its database. Other services can only interact with the data through the service's API. This ensures:
Benefits
**Decentralized data management.** Each team chooses the best database for their service's needs. The order service might use PostgreSQL for ACID compliance. The analytics service might use a columnar store or data warehouse. The product catalog might use Elasticsearch for full-text search.
**Independent scalability.** Services with high data volume can scale independently. The order database can be scaled up without affecting the payment database.
**Fault isolation.** A failure in one service's database does not affect other services. If the inventory database goes down, the order service can still handle requests using cached or default inventory data.
**Team autonomy.** Service teams can change their schema, migrate databases, or switch technologies without coordinating with other teams. This speed is a primary goal of microservices.
**Polyglot persistence.** Different data storage technologies excel at different tasks. SQL, NoSQL, key-value stores, graph databases, and time-series databases each have strengths. The database per service pattern lets you choose the right tool for each job.
Challenges
Data Consistency Across Services
In a monolith, a single ACID transaction can update multiple tables atomically. In a microservices architecture with separate databases, cross-service transactions are not possible.
**Solution:** Use the saga pattern. A saga is a sequence of local transactions, each within a single service. If a step fails, compensating transactions undo prior steps. Sagas provide eventual consistency without distributed transactions.
Queries Across Services
A query like "find all orders containing a product that is currently on sale" spans the order service and the product service. With separate databases, a simple JOIN is impossible.
**Solutions:**
Data Duplication
Multiple services might need the same data. The order service needs customer names. The shipping service needs customer addresses. Different pieces of customer data are owned by different services.
**Solution:** Each piece of data has a single source of truth (the service that owns it). Other services store cached or derived copies. Event-driven updates keep copies synchronized. Accept that some data duplication is normal and intentional in distributed systems.
Polyglot Persistence in Practice
Choosing the right database for each service:
The cost of polyglot persistence is operational complexity. Each database type has different management, monitoring, and backup requirements. Evaluate whether the benefits justify the additional operational burden.
Eventual Consistency and Compensations
Embrace eventual consistency between services. When a user places an order:
2. Order service publishes `OrderCreated` event.
3. Inventory service consumes the event and reserves inventory (may take milliseconds).
4. Payment service consumes the event and processes payment (may take seconds).
Between steps 1 and 4, the order is in a "pending" state. This window of inconsistency is normal. The user sees "Order placed, processing payment" rather than an immediate confirmation.
Design your compensation logic carefully. If payment fails:
Transactional Boundaries
Define clear transactional boundaries for each service. A service should encapsulate data that changes together. If two pieces of data are always read and written together, they might belong in the same service.
A common mistake is splitting data too finely. If every order operation needs to query customer data, the order and customer services are tightly coupled by data dependencies. In such cases, reconsider the service boundaries.
Migration Strategy
Migrating from a shared database to database per service:
2. **Extract one service at a time**. Do not attempt a big bang migration.
3. **Use database views or triggers** as temporary bridges during migration.
4. **Implement event-driven synchronization** to keep data in sync during the transition.
5. **Cut over traffic** service by service.
Summary
The database per service pattern is essential for microservices that need independent scalability, team autonomy, and polyglot persistence. Embrace eventual consistency across services and use the saga pattern for multi-service operations. Accept data duplication as a normal part of distributed systems. Choose database technologies based on each service's specific requirements, but be mindful of the operational complexity that comes with multiple database systems.