Introduction
In monolithic applications, a single shared database often serves the entire application. But in a microservices architecture, this approach becomes a bottleneck. Enter the Database per Service Pattern—a foundational principle that assigns each service its own private database, ensuring data ownership, loose coupling, and scalability.
This tutorial explains the Database per Service pattern in depth using Java-based microservices. You'll learn why this pattern matters, how to implement it, and how to handle challenges like cross-service queries.
🧠 What Is the Database per Service Pattern?
The Database per Service pattern assigns each microservice its own dedicated data store. This enables services to operate independently, even at the data level.
UML-style Diagram
[Order Service] ---> [Order DB]
[Customer Service] ---> [Customer DB]
[Inventory Service] ---> [Inventory DB]
🎯 Intent and Participants
Intent:
To promote loose coupling and high autonomy by isolating the database schema of each microservice.
Participants:
- Service: Business logic holder with exclusive access to its DB.
- Database: Tied to the service; not accessible by other services.
- API Layer: Fetches related data across services when needed.
🌍 Real-World Use Cases
- E-commerce platforms where orders, products, and users are distinct services.
- Banking systems where accounts and transactions are separate microservices.
- Logistics apps with services like shipment, tracking, and billing.
🧰 Implementation Strategies in Java
1. Spring Boot Services with Dedicated PostgreSQL Databases
- customer-service uses
customer_db
- order-service uses
order_db
application.yml for customer-service
spring:
datasource:
url: jdbc:postgresql://localhost:5432/customer_db
username: customer_user
password: secret
application.yml for order-service
spring:
datasource:
url: jdbc:postgresql://localhost:5432/order_db
username: order_user
password: secret
2. API Composition to Fetch Related Data
Instead of joining tables across DBs, use API composition.
public class OrderDTO {
private Order order;
private Customer customer;
}
// order-service calls customer-service
RestTemplate restTemplate = new RestTemplate();
Customer customer = restTemplate.getForObject("http://customer-service/customers/" + order.getCustomerId(), Customer.class);
✅ Pros and Cons
Pros | Cons |
---|---|
Strong service autonomy | Harder to perform cross-service joins |
Better scalability and resilience | Data duplication may occur |
Easier versioning and schema changes | Eventual consistency instead of strong ACID |
Better fault isolation | Requires inter-service communication for joins |
❌ Anti-Patterns
- Shared Database Across Services: Defeats microservice independence.
- Direct DB Access Between Services: Breaks encapsulation and coupling.
- Too Many DBs for Similar Services: Overhead in setup/maintenance.
🔁 Comparison with Similar Patterns
Pattern | Description |
---|---|
Database per Service | Each service has its own DB (preferred for autonomy) |
Shared Database | All services share a common DB (not recommended) |
Shared Schema per Service | Logical schema isolation, but still shared DB |
API Composition | Fetch related data via service API, not joins |
Event-Driven Sync | Services sync using events via Kafka or RabbitMQ |
💻 Java Code Example – OrderService Using RestTemplate
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public OrderDTO getOrderDetails(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
Customer customer = restTemplate.getForObject("http://customer-service/customers/" + order.getCustomerId(), Customer.class);
return new OrderDTO(order, customer);
}
}
🔧 Refactoring Legacy Monolith
Before (Monolithic)
SELECT o.id, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id;
After (Microservices with API Composition)
order-service
fetches order- Makes REST call to
customer-service
for customer details
🌟 Best Practices
- Use service APIs or events instead of direct DB access.
- Define clear ownership of each DB.
- Apply schema versioning per service.
- Use caching to reduce cross-service calls.
- Document data contracts clearly.
🧠 Real-World Analogy
Imagine separate departments in a company each maintaining its own records: HR has employee files, Finance has payment records, and IT has access logs. They don’t directly access each other’s files but share summaries via email or reports—just like services using API calls.
☕ Java Feature Relevance
- Java Records (16+): Lightweight DTOs for composed API responses.
- Feign Clients: Declarative REST client for inter-service communication.
- Spring Profiles: Handle different DB credentials/configs per service.
🔚 Conclusion & Key Takeaways
The Database per Service pattern enables scalable, maintainable microservices by enforcing data ownership. While it introduces challenges in querying and consistency, it vastly improves autonomy and resilience.
✅ Summary
- Isolate each service's data using dedicated databases.
- Use API composition or eventing to sync data.
- Avoid direct DB sharing or joins.
- Monitor and secure each database independently.
❓ FAQ – Database per Service Pattern in Java
1. Why is sharing a database across microservices a bad idea?
It tightly couples services and makes schema evolution difficult.
2. Can services ever access each other’s databases?
No. They should communicate via APIs or events.
3. What database is best for this pattern?
PostgreSQL, MySQL, MongoDB—anything service-scoped.
4. How do I handle joins across services?
Use API composition or CQRS with event sync.
5. Is this suitable for small applications?
It can be overkill—use it when scaling is a requirement.
6. How does security work in this model?
Each DB has its own user/password, roles, and access controls.
7. What’s the alternative to API composition?
Event-driven replication via Kafka or messaging brokers.
8. Is eventual consistency a problem?
Only for critical financial operations—use compensating transactions.
9. Can I still use ORMs like JPA?
Yes. Each service can use JPA with its own datasource.
10. Should I use schema-per-service or database-per-service?
Prefer database-per-service for stronger isolation.