Skip to main content

Database Per Service

Isolate service data with dedicated databases to maintain independence and prevent tight coupling.

TL;DR

Each microservice should own and manage its own database. This principle—database per service—prevents tight coupling, allows services to evolve independently, and enables teams to choose appropriate storage technologies for their specific needs. However, it introduces data consistency challenges that must be solved through patterns like eventual consistency, sagas, and event-driven architecture. The tradeoff is worth it: independence and scalability outweigh the complexity of distributed data coordination.

Learning Objectives

  • Understand why shared databases create unacceptable coupling in microservices
  • Design service boundaries around data ownership
  • Implement database isolation strategies
  • Choose appropriate storage technologies per service
  • Manage cross-service queries and data consistency
  • Navigate the tradeoffs of data isolation

Motivating Scenario

Two teams share a database: users and orders tables. The orders team needs to add a new column for a feature. They request a migration. The users team delays approval (they're busy). Meanwhile, the orders team is blocked. Later, a query on the users table runs long and locks the entire database—orders performance suffers even though nothing changed in their code. This coupling is unacceptable in microservices.

Core Concepts

Why Shared Databases Fail

Shared databases create invisible dependencies. Even without direct coupling, teams are coupled through schema evolution, locking, resource contention, and operational decisions. Each team can't move independently. One team's schema change impacts everyone. One team's runaway query affects all services.

Database Per Service Pattern

Each service owns a database schema and is the sole reader/writer of that data. No other service directly queries the database. If services need data from each other, they use APIs. This enforces data boundaries and service independence.

Polyglot Persistence

Different services have different data needs. A service handling real-time metrics might use time-series databases. An analytics service might use columnar storage. A transactional service might use relational databases. Database per service enables polyglot persistence—choosing the right tool for each job.

Data Consistency Challenges

The cost of isolation is complexity: how do services stay consistent? Solutions include eventual consistency through events, sagas for distributed transactions, outbox patterns, and change data capture. These are explored in subsequent sections.

Practical Example

# ❌ POOR - Shared database with direct queries
class UserService:
def get_user(self, user_id):
# Direct query to shared database
return db.query("SELECT * FROM users WHERE id = %s", user_id)

class OrderService:
def create_order(self, user_id, items):
# Tight coupling: Orders service directly accesses users table
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
if not user:
raise ValueError("User not found")
# Create order...

# ✅ EXCELLENT - Database per service with API boundaries
class UserService:
def __init__(self, user_db):
self.db = user_db # Dedicated database

def get_user(self, user_id):
return self.db.query("SELECT * FROM users WHERE id = %s", user_id)

def expose_api_endpoint(self, app):
@app.route('/users/<user_id>')
def get_user_endpoint(user_id):
return self.get_user(user_id)

class OrderService:
def __init__(self, order_db, user_service_client):
self.db = order_db # Dedicated database
self.user_client = user_service_client # Uses API

def create_order(self, user_id, items):
# Access user data via API, not direct query
try:
user = self.user_client.get_user(user_id)
except UserNotFoundError:
raise ValueError("User not found")

order = Order(user_id=user_id, items=items)
self.db.insert(order)
return order

When to Use / When Not to Use

When to Use Database Per Service
  1. Services with independent data models and different scaling needs
  2. Teams working in parallel who need autonomy
  3. Systems where services need different storage technologies
  4. Mature microservices architectures with event infrastructure
  5. High-scale systems where shared databases become bottlenecks
When NOT to Use (Use Shared Database)
  1. Early-stage monoliths being built (premature optimization)
  2. Simple systems with few services and minimal scaling needs
  3. When distributed transactions are essential and sagas are infeasible
  4. Systems without event infrastructure or event-driven capabilities
  5. Teams lacking operational maturity for distributed systems

Patterns and Pitfalls

Design Review Checklist

  • Each service has sole write access to its database schema
  • Services access each other's data through APIs, not direct database queries
  • Schema changes are owned by the service and don't require approval from other teams
  • Data consistency mechanisms (events, sagas) are designed for critical workflows
  • Read replicas or caching strategies are considered for frequently accessed data
  • Database selection is justified for each service's use case
  • Monitoring alerts for data consistency issues are in place

Self-Check

  • Can you explain why shared databases create coupling in microservices?
  • What are the consistency tradeoffs of database per service, and how do you address them?
  • How would you handle a query that needs data from multiple services' databases?
One Takeaway

Database per service is a constraint that forces good design. Yes, it's harder than shared databases. But that difficulty is intentional—it prevents accidental tight coupling that becomes architectural debt.

Next Steps

  • Implement eventual consistency patterns for cross-service data synchronization
  • Explore CQRS and event sourcing for complex data consistency requirements
  • Design change data capture (CDC) systems to keep services synchronized
  • Study saga patterns for distributed transactions

References

  • Sam Newman, Building Microservices (O'Reilly)
  • Chris Richardson, Microservices Patterns: Pattern Language for Microservices
  • Martin Fowler & James Lewis, Microservices