What is Database Per Service Pattern in Microservices?
Database Per Service Pattern is a design pattern used in Microservices Architecture where each microservice owns and manages its own separate database.
Instead of sharing one common database among all services, every microservice maintains its own private database that cannot be directly accessed by other services.
This pattern is one of the most important best practices in Microservices Architecture because it helps achieve:
- Loose coupling
- Independent deployment
- Independent scalability
- Better fault isolation
- Service autonomy
Simple Understanding of Database Per Service Pattern
Imagine a large company with different departments:
- HR Department
- Finance Department
- Sales Department
- Support Department
Each department maintains its own records and files independently.
The Finance Department should not directly modify HR files. If information is needed, departments communicate officially.
Similarly, in Microservices Architecture:
- User Service maintains User Database
- Order Service maintains Order Database
- Payment Service maintains Payment Database
Services should communicate using APIs or events instead of directly accessing other databases.
Why Database Per Service Pattern is Needed
In traditional Monolithic Architecture, all modules usually share one database.
Monolithic Example
---------------------------------------------------- | Shared Database | ---------------------------------------------------- | Users | Orders | Payments | Courses | Products | ----------------------------------------------------
Problems:
- Tight coupling between modules
- Difficult schema changes
- Deployment dependency issues
- Scalability limitations
- High risk of system-wide impact
To solve these problems, Microservices introduced Database Per Service Pattern.
Database Per Service Architecture
-----------------------------------------------------
| API Gateway |
-----------------------------------------------------
| | |
v v v
-----------------------------------------------------
| User Service | Order Service | Payment Service |
-----------------------------------------------------
| | |
v v v
-----------------------------------------------------
| User DB | Order DB | Payment DB |
-----------------------------------------------------
Each service owns its own database independently.
Main Rule of Database Per Service Pattern
The most important rule is:
A microservice should directly access only its own database.
Other services must communicate through:
- REST APIs
- Feign Client
- Kafka events
- RabbitMQ messages
Example Without Database Per Service Pattern
Suppose all services share one database:
--------------------------------------------------- | Shared Database | --------------------------------------------------- | Users | Orders | Payments | Notifications | ---------------------------------------------------
Problems:
- Payment Service may directly modify Order tables
- Schema changes affect all services
- Tight dependency between services
- Difficult maintenance
Example With Database Per Service Pattern
User Service ---> User Database Order Service ---> Order Database Payment Service ---> Payment Database Notification Service ---> Notification Database
Each service independently manages its own data.
Real-Time Example
Suppose an online learning platform contains:
- Auth Service
- Course Service
- Payment Service
- Interview Service
Database Structure
Auth Service ---> auth_db Course Service ---> course_db Payment Service ---> payment_db Interview Service ---> interview_db
Each service independently maintains its own schema and database logic.
How Services Communicate with Separate Databases
Suppose Order Service needs payment details.
Wrong Approach:
Order Service ---> Directly Access Payment DB
Correct Approach:
Order Service ---> Payment Service API
Payment Service internally accesses its own database and returns the response.
Advantages of Database Per Service Pattern
1. Loose Coupling
Services become independent because they do not directly depend on other databases.
2. Independent Deployment
Database schema changes affect only one service.
3. Better Scalability
Each database can scale independently.
Example
Payment Service may require high-performance database scaling while Notification Service may not.
4. Technology Flexibility
Different services can use different databases.
Example
User Service -> MySQL Analytics Service -> MongoDB Cache Service -> Redis
5. Better Fault Isolation
Database failure in one service usually does not affect other services.
6. Improved Security
Services cannot directly modify another service database.
7. Easier Maintenance
Smaller independent databases are easier to manage.
Challenges of Database Per Service Pattern
1. Distributed Transactions
Managing transactions across multiple databases becomes difficult.
Example
- Order created successfully
- Payment failed
Rollback becomes complicated.
Solution
- Saga Pattern
- Event-driven architecture
2. Data Duplication
Some data may exist in multiple services.
3. Complex Reporting
Generating reports across multiple databases becomes difficult.
4. Eventual Consistency
Data synchronization between services may not happen instantly.
5. Increased Infrastructure Complexity
Managing many databases increases operational complexity.
How Services Share Data
Since databases are isolated, services share information using:
- REST APIs
- Feign Client
- Kafka events
- RabbitMQ messaging
Example Using REST API
Order Service
|
v
Payment Service API
|
v
Payment Database
Example Using Event-Driven Architecture
Order Created Event
|
v
Kafka
|
v
Payment Service
Services communicate asynchronously using events.
Database Types Used in Microservices
| Database | Usage |
|---|---|
| MySQL | Relational transactions |
| PostgreSQL | Advanced SQL support |
| MongoDB | Document-based storage |
| Redis | Caching |
| Cassandra | Large-scale distributed storage |
Database Per Service vs Shared Database
| Feature | Shared Database | Database Per Service |
|---|---|---|
| Coupling | Tightly coupled | Loosely coupled |
| Scalability | Limited | Independent scaling |
| Deployment | Dependent | Independent |
| Technology Flexibility | Limited | High flexibility |
| Fault Isolation | Poor | Better |
| Complexity | Simple initially | More complex |
Best Practices for Database Per Service Pattern
- Never allow direct database access across services
- Use APIs or events for communication
- Implement Saga Pattern for distributed transactions
- Use proper monitoring and backups
- Secure databases independently
- Enable database scalability where needed
Real-Time Company Example
Netflix uses Database Per Service Pattern extensively.
Different services maintain separate databases for:
- User profiles
- Recommendations
- Streaming history
- Billing
This allows Netflix to scale and deploy services independently.
Interview Ready Answer
Database Per Service Pattern is a Microservices design pattern where each microservice owns and manages its own separate database. Other services cannot directly access that database and must communicate using APIs or messaging systems. This pattern helps achieve loose coupling, independent deployment, scalability, technology flexibility, and fault isolation. However, it also introduces challenges such as distributed transactions, eventual consistency, and increased operational complexity.
Frequently Asked Questions
Why should each microservice have its own database?
To achieve loose coupling, independent deployment, and better scalability.
Can one service directly access another service database?
No. Services should communicate using APIs or messaging systems instead of direct database access.
What is the biggest challenge in Database Per Service Pattern?
Managing distributed transactions and maintaining data consistency.
Can different microservices use different databases?
Yes. Each service can choose the database best suited for its requirements.
Which patterns help manage distributed transactions?
Saga Pattern and Event-Driven Architecture are commonly used.
Database Per Service Pattern Coding Example
In this example, we will create two microservices:
- Order Service with order_db
- Payment Service with payment_db
Order Service will not directly access Payment Database. Instead, Order Service will call Payment Service using Feign Client.
Architecture
Order Service
|
| Feign Client API Call
v
Payment Service
|
v
Payment Database
1. Order Service Database Configuration
spring.application.name=ORDER-SERVICE server.port=8081 spring.datasource.url=jdbc:mysql://localhost:3306/order_db spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
2. Payment Service Database Configuration
spring.application.name=PAYMENT-SERVICE server.port=8082 spring.datasource.url=jdbc:mysql://localhost:3306/payment_db spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
3. Order Entity in Order Service
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private Double amount;
private String status;
public Order() {
}
public Order(String productName, Double amount, String status) {
this.productName = productName;
this.amount = amount;
this.status = status;
}
// getters and setters
}
4. Payment Entity in Payment Service
@Entity
@Table(name = "payments")
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long orderId;
private Double amount;
private String paymentStatus;
public Payment() {
}
public Payment(Long orderId, Double amount, String paymentStatus) {
this.orderId = orderId;
this.amount = amount;
this.paymentStatus = paymentStatus;
}
// getters and setters
}
5. Order Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}
6. Payment Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
}
7. Payment Request DTO
public class PaymentRequest {
private Long orderId;
private Double amount;
public PaymentRequest() {
}
public PaymentRequest(Long orderId, Double amount) {
this.orderId = orderId;
this.amount = amount;
}
// getters and setters
}
8. Payment Response DTO
public class PaymentResponse {
private Long paymentId;
private Long orderId;
private String paymentStatus;
public PaymentResponse() {
}
public PaymentResponse(Long paymentId, Long orderId, String paymentStatus) {
this.paymentId = paymentId;
this.orderId = orderId;
this.paymentStatus = paymentStatus;
}
// getters and setters
}
9. Payment Controller in Payment Service
@RestController
@RequestMapping("/payments")
public class PaymentController {
private final PaymentRepository paymentRepository;
public PaymentController(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
@PostMapping
public PaymentResponse makePayment(@RequestBody PaymentRequest request) {
Payment payment = new Payment(
request.getOrderId(),
request.getAmount(),
"SUCCESS"
);
Payment savedPayment = paymentRepository.save(payment);
return new PaymentResponse(
savedPayment.getId(),
savedPayment.getOrderId(),
savedPayment.getPaymentStatus()
);
}
}
10. Enable Feign Client in Order Service
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
11. Payment Feign Client in Order Service
@FeignClient(name = "PAYMENT-SERVICE", url = "http://localhost:8082")
public interface PaymentClient {
@PostMapping("/payments")
PaymentResponse makePayment(@RequestBody PaymentRequest request);
}
12. Order Service Logic
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentClient paymentClient;
public OrderService(OrderRepository orderRepository,
PaymentClient paymentClient) {
this.orderRepository = orderRepository;
this.paymentClient = paymentClient;
}
public Order placeOrder(String productName, Double amount) {
Order order = new Order(productName, amount, "CREATED");
Order savedOrder = orderRepository.save(order);
PaymentRequest paymentRequest = new PaymentRequest(
savedOrder.getId(),
savedOrder.getAmount()
);
PaymentResponse paymentResponse =
paymentClient.makePayment(paymentRequest);
if ("SUCCESS".equals(paymentResponse.getPaymentStatus())) {
savedOrder.setStatus("PAYMENT_SUCCESS");
} else {
savedOrder.setStatus("PAYMENT_FAILED");
}
return orderRepository.save(savedOrder);
}
}
13. Order Controller
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public Order placeOrder(@RequestBody OrderRequest request) {
return orderService.placeOrder(
request.getProductName(),
request.getAmount()
);
}
}
14. Order Request DTO
public class OrderRequest {
private String productName;
private Double amount;
public OrderRequest() {
}
public OrderRequest(String productName, Double amount) {
this.productName = productName;
this.amount = amount;
}
// getters and setters
}
15. API Testing
POST http://localhost:8081/orders
{
"productName": "Java Course",
"amount": 499.0
}
16. What Happens Internally?
- Client calls Order Service.
- Order Service saves order data in order_db.
- Order Service calls Payment Service using Feign Client.
- Payment Service saves payment data in payment_db.
- Payment Service returns payment status.
- Order Service updates order status.
17. Important Point
Order Service never directly connects to payment_db. Payment Service never directly connects to order_db.
This is the correct implementation of Database Per Service Pattern.