
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:
| Service | Database | Lý do |
|---|---|---|
| Order | PostgreSQL | ACID transactions, relational data, complex queries |
| Product Catalog | MongoDB | Flexible schema, nested documents, varied product types |
| User Session | Redis | In-memory, sub-millisecond access, auto-expire (TTL) |
| Search | Elasticsearch | Full-text search, inverted index, faceted search |
| Activity Feed | Apache Cassandra | High write throughput, time-series, distributed |
| Recommendation | Neo4j | Graph relationships ("users who bought X also bought Y") |
| Shopping Cart | Redis / DynamoDB | Key-value, fast access, ephemeral data |
| Analytics | ClickHouse | Columnar, OLAP, aggregate queries |
| File/Image | S3 / MinIO | Object 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
| Strategy | Pros | Cons | Use Case |
|---|---|---|---|
| API Composition | Đơn giản, real-time data | Latency (multiple calls), partial failure | Dashboard, admin UI |
| CQRS + Materialized View | Fast reads, pre-composed | Eventual consistency, complexity | Customer-facing, search |
| Data Replication (events) | Fast, local query | Stale data, storage duplication | Read-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
| Concept | Key Point |
|---|---|
| Database per Service | Mỗi service sở hữu DB riêng, không chia sẻ |
| Polyglot Persistence | Chọn DB phù hợp cho từng service |
| API Composition | Query cross-service bằng cách aggregate API calls |
| CQRS + View | Tạo read-optimized view cho query phức tạp |
| Data Ownership | Mỗi data có một service owner duy nhất |
| Data Snapshot | Copy 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.