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

Bài 8: Database per Service & Polyglot Persistence

Nguyên tắc Database per Service, tại sao không chia sẻ database, Polyglot Persistence (chọn DB phù hợp cho từng service), data ownership và cross-service data query strategies.

🏗️ Kiến trúc — Bài 8 Bài 8: Database per Service & Polyglot Persistence

Cloud Native Microservices Architecture

Phần 3: Data Management trong Microservices

xdev.asia

Bài 8: Database per Service & Polyglot Persistence

Giới thiệu

Trong monolith, tất cả module chia sẻ một database. Trong microservices, mỗi service sở hữu database riêng. Nguyên tắc này là nền tảng để đạt loose coupling nhưng đồng thời tạo ra nhiều thách thức mới về data consistency.


1. Database per Service Pattern

1.1 Nguyên tắc

✅ 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 Tại sao không chia sẻ 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 Chọn Database phù hợp

Mỗi service chọn database tối ưu cho đặc tính dữ liệu của mình:

ServiceDatabaseLý do
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 Ví dụ thực tế: 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 Vấn đề

Khi cần hiển thị order details bao gồm thông tin customer và product:

❌ 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 Giải pháp: 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 Giải pháp: 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 So sánh strategies

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

4. Data Ownership

4.1 Quy tắc

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 Từ Shared DB sang 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. Tổng kết

ConceptKey Point
Database per ServiceMỗi service sở hữu DB riêng, không chia sẻ
Polyglot PersistenceChọn DB phù hợp cho từng service
API CompositionQuery cross-service bằng cách aggregate API calls
CQRS + ViewTạo read-optimized view cho query phức tạp
Data OwnershipMỗi data có một service owner duy nhất
Data SnapshotCopy data cần thiết tại thời điểm transaction

Bài tiếp theo: Event Sourcing & CQRS — Lưu trạng thái dưới dạng events và tách biệt read/write model.