Chuyển đến nội dung chính

Lesson 8: Database per Service & Polyglot Persistence

Database per Service principles, why not share databases, Polyglot Persistence (choose the appropriate DB for each service), data ownership and cross-service data query strategies.

🏗️ Architecture — Lesson 8 Lesson 8: Database per Service & Polyglot Persistence

Cloud Native Microservices Architecture

Part 3: Data Management in Microservices

xdev.asia

Lesson 8: Database per Service & Polyglot Persistence

Introduction

In monolith, all modules share one database. In microservices, each service owns its own database. This principle is the foundation for achieving loose coupling but at the same time creates many new data consistency challenges.


1. Database per Service Pattern

1.1 Principles

✅ Database per Service:
┌────────────┐    ┌────────────┐    ┌────────────┐
│   Order    │    │  Payment   │    │  Catalog   │
│  Service   │    │  Service   │    │  Service   │
└─────┬──────┘    └─────┬──────┘    └─────┬──────┘
      │                 │                 │
┌─────▼──────┐    ┌─────▼──────┐    ┌─────▼──────┐
│ PostgreSQL │    │ PostgreSQL │    │  MongoDB   │
│  (orders)  │    │ (payments) │    │ (products) │
└────────────┘    └────────────┘    └────────────┘

Quy tắc: KHÔNG truy cập DB của service khác trực tiếp.
Muốn data từ Payment? → Gọi Payment API.

1.2 Why not share Database?

❌ Shared Database:
┌────────────┐    ┌────────────┐
│   Order    │    │  Payment   │
│  Service   │    │  Service   │
└─────┬──────┘    └─────┬──────┘
      │                 │
      └────────┬────────┘
         ┌─────▼──────┐
         │ Shared DB  │
         │ (all tables)│
         └────────────┘

Vấn đề:
├── Schema coupling: Payment đổi schema → Order bị broken
├── Performance coupling: Query nặng từ Order → Payment bị chậm
├── Deployment coupling: DB migration phải coordinate cả 2 team
├── Scaling coupling: Không thể scale DB riêng cho từng service
└── Technology coupling: Tất cả phải dùng cùng DB engine

1.3 Isolation Strategies

Strategy 1: Separate Database (khuyến nghị)
├── Mỗi service một database instance
├── Cách ly hoàn toàn
└── Chi phí cao hơn nhưng an toàn nhất

Strategy 2: Separate Schema
├── Cùng database instance, khác schema
├── Cách ly ở mức schema
└── Chi phí thấp hơn, phù hợp start

Strategy 3: Separate Tables
├── Cùng schema, khác tables
├── Cách ly yếu nhất
└── Chỉ phù hợp giai đoạn đầu migration

2. Polyglot Persistence

2.1 Choose the appropriate Database

Each service chooses the optimal database for its data characteristics:

ServiceDatabaseReason
OrderPostgreSQLACID transactions, relational data, complex queries
Product CatalogMongoDBFlexible schema, nested documents, varied product types
User SessionRedisIn-memory, sub-millisecond access, auto-expire (TTL)
SearchElasticsearchFull-text search, inverted index, faceted search
Activity FeedApache CassandraHigh write throughput, time-series, distributed
RecommendationNeo4jGraph relationships ("users who bought X also bought Y")
Shopping CartRedis/DynamoDBKey-value, fast access, ephemeral data
AnalyticsClickHouseColumnar, OLAP, aggregate queries
File/ImageS3/MinIOObject storage, unlimited scale

2.2 Practical example: E-Commerce

┌──────────┐  PostgreSQL   ┌──────────┐  MongoDB
│  Order   │──────────────▶│ Catalog  │──────────▶
│  Service │  (orders,     │ Service  │  (products,
└──────────┘   line_items) └──────────┘   variants)

┌──────────┐  Redis        ┌──────────┐  Elasticsearch
│  Cart    │──────────────▶│  Search  │──────────▶
│  Service │  (cart:{uid})  │ Service  │  (products index)
└──────────┘               └──────────┘

┌──────────┐  PostgreSQL   ┌──────────┐  ClickHouse
│ Payment  │──────────────▶│Analytics │──────────▶
│ Service  │  (payments,   │ Service  │  (events,
└──────────┘   refunds)    └──────────┘   aggregates)

3. Cross-Service Data Query

3.1 Problem

When you need to display order details including customer and product information:

❌ Trước (monolith): 
  SELECT o.*, c.name, p.title 
  FROM orders o
  JOIN customers c ON o.customer_id = c.id
  JOIN products p ON oi.product_id = p.id

✅ Sau (microservices):
  Order, Customer, Product ở databases khác nhau → Không thể JOIN!

3.2 Solution: API Composition

API Gateway / BFF / Composite Service:

1. GET /orders/O-001     → Order Service    → {order_id, customer_id, items}
2. GET /customers/C-042  → Customer Service → {name, email}
3. GET /products/P-100   → Product Service  → {title, image}

4. Compose response:
{
  "order": {
    "id": "O-001",
    "customer": {"name": "Nguyen Van A", "email": "[email protected]"},
    "items": [
      {"product": {"title": "iPhone 16", "image": "..."}, "quantity": 1}
    ]
  }
}

3.3 Solution: CQRS + Materialized View

Tạo read-optimized view bằng cách subscribe events:

Order.Created ──▶ ┌─────────────────────┐
Customer.Updated ──▶│  Order Detail View  │
Product.Updated  ──▶│  (Elasticsearch)    │
                   │                     │
                   │  {order + customer  │
                   │   + product details}│
                   └─────────────────────┘

Query: GET /order-details/O-001 → Trả kết quả đã composed sẵn

3.4 Compare strategies

StrategyProsConsUse Case
API CompositionSimple, real-time dataLatency (multiple calls), partial failureDashboard, admin UI
CQRS + Materialized ViewFast reads, pre-composedEventual consistency, complexityCustomer-facing, search
Data Replication (events)Fast, local queriesStale data, storage duplicationRead-heavy services

4. Data Ownership

4.1 Rules

Mỗi piece of data có MỘT owner duy nhất:

Customer data → Customer Service (owner)
  ├── Order Service: giữ customer_id (reference)
  ├── Payment Service: giữ customer_id (reference)
  └── Notification Service: subscribe CustomerUpdated event

Price data → Catalog Service (owner)
  └── Order Service: snapshot giá tại thời điểm order
       (không query lại, vì giá có thể thay đổi)

4.2 Data Snapshot Pattern

Khi tạo Order, snapshot data cần thiết:

Order {
  id: "O-001",
  customer_snapshot: {        ← Copy tại thời điểm order
    name: "Nguyen Van A",
    address: "123 ABC"
  },
  items: [{
    product_id: "P-100",
    title_snapshot: "iPhone",  ← Copy tại thời điểm order
    price_snapshot: 25000000   ← Giá tại thời điểm order
  }]
}

→ Customer đổi address sau đó? Order vẫn giữ address cũ (đúng)
→ Product tăng giá? Order vẫn giữ giá cũ (đúng)

5. Database Migration Strategy

5.1 From Shared DB to Database per Service

Phase 1: Identify boundaries
  Shared DB → Xác định tables thuộc service nào

Phase 2: Create APIs
  Service A gọi Service B qua API thay vì JOIN

Phase 3: Sync data
  Dual-write hoặc CDC để sync trong quá trình migration

Phase 4: Split databases
  Move tables sang database riêng

Phase 5: Remove old connections
  Xoá direct DB access, chỉ giữ API calls

6. Summary

ConceptsKey Point
Database per ServiceEach service owns its own database, not shared
Polyglot PersistenceChoose the appropriate DB for each service
API CompositionCross-service queries by aggregate API calls
CQRS + ViewCreate read-optimized views for complex queries
Data OwnershipEach data has a unique service owner
Data SnapshotsCopy the data needed at the time of transaction

Next article: Event Sourcing & CQRS — Save state as events and separate read/write model.