0
0
Microservicessystem_design~7 mins

Why each service owns its data in Microservices - Why This Architecture

Choose your learning style9 modes available
Problem Statement
When multiple services share the same database or data store, changes by one service can unexpectedly break others. This tight coupling causes deployment delays, data corruption, and scaling problems because services depend on the same data structure and access patterns.
Solution
Each service manages its own private database or data store, owning its data exclusively. Services communicate with each other through APIs or events to share information, avoiding direct database sharing. This separation allows independent development, deployment, and scaling without risking data conflicts.
Architecture
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ Service A   │       │ Service B   │       │ Service C   │
│ ┌─────────┐ │       │ ┌─────────┐ │       │ ┌─────────┐ │
│ │ DB A    │ │       │ │ DB B    │ │       │ │ DB C    │ │
│ └─────────┘ │       │ └─────────┘ │       │ └─────────┘ │
└─────┬───────┘       └─────┬───────┘       └─────┬───────┘
      │ API/Event            │ API/Event            │ API/Event
      └─────────────────────┴─────────────────────┴

This diagram shows three microservices, each with its own private database. They communicate only via APIs or events, never sharing databases directly.

Trade-offs
✓ Pros
Enables independent service deployment and scaling without affecting others.
Prevents data corruption and conflicts caused by shared database access.
Improves fault isolation; one service's data issues don't cascade.
Allows each service to choose the best database technology for its needs.
✗ Cons
Requires designing APIs or event contracts for data sharing, adding complexity.
Data duplication may occur, increasing storage and synchronization overhead.
Cross-service queries become harder, often requiring data aggregation layers.
Use when building microservices that require independent lifecycle, scalability, and fault isolation, especially at scale beyond hundreds of requests per second.
Avoid when the system is small with tightly coupled components or when strong transactional consistency across services is mandatory and cannot be handled via eventual consistency.
Real World Examples
Amazon
Each microservice owns its data to allow independent scaling and deployment, avoiding database contention and enabling rapid feature development.
Netflix
Services own their data stores to isolate failures and optimize data models per service, improving resilience and performance.
Uber
Separate data ownership per service helps manage complex domain boundaries and supports diverse data storage technologies.
Code Example
This code shows how sharing a database directly causes tight coupling and data conflicts. After applying the pattern, each service owns its data and communicates via events, enabling independent evolution and reducing risk.
Microservices
### Before: Two services sharing the same database table directly
class OrderService:
    def create_order(self, order_data):
        # Directly insert into shared orders table
        db.execute("INSERT INTO orders ...", order_data)

class PaymentService:
    def update_payment_status(self, order_id, status):
        # Directly update shared orders table
        db.execute("UPDATE orders SET payment_status=? WHERE id=?", (status, order_id))


### After: Each service owns its own database and communicates via API

# Order Service with private DB
class OrderService:
    def __init__(self, db, event_bus):
        self.db = db
        self.event_bus = event_bus

    def create_order(self, order_data):
        self.db.insert_order(order_data)
        # Notify Payment Service via event
        self.event_bus.publish('order_created', order_data)

# Payment Service with private DB
class PaymentService:
    def __init__(self, db):
        self.db = db

    def on_order_created(self, order_data):
        self.db.create_payment_record(order_data['id'])

    def update_payment_status(self, order_id, status):
        self.db.update_payment_status(order_id, status)

# Event bus simulates communication
class EventBus:
    def __init__(self):
        self.subscribers = {}
    def publish(self, event, data):
        for handler in self.subscribers.get(event, []):
            handler(data)
    def subscribe(self, event, handler):
        self.subscribers.setdefault(event, []).append(handler)

# Wiring up
# Assuming db instances are provided
order_db = ...  # some database instance for orders
payment_db = ...  # some database instance for payments

event_bus = EventBus()
order_service = OrderService(order_db, event_bus)
payment_service = PaymentService(payment_db)
event_bus.subscribe('order_created', payment_service.on_order_created)

# Explanation:
# The OrderService owns its orders database and publishes events.
# The PaymentService owns its payment database and reacts to events.
# No direct database sharing occurs.
OutputSuccess
Alternatives
Shared Database
Multiple services access the same database schema directly without data ownership boundaries.
Use when: Choose when the system is small, teams are tightly coordinated, and strong ACID transactions across services are required.
Database per Service with Shared Schema
Each service has its own database but shares the same schema and data model.
Use when: Choose when teams want some isolation but need uniform data structure for easier integration.
Summary
Sharing a database among services causes tight coupling and deployment risks.
Each service owning its data enables independent development, deployment, and scaling.
Services communicate via APIs or events to share data while maintaining ownership boundaries.