
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:
| Service | Database | Reason |
|---|---|---|
| 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 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
| Strategy | Pros | Cons | Use Case |
|---|---|---|---|
| API Composition | Simple, 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 queries | Stale data, storage duplication | Read-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
| Concepts | Key Point |
|---|---|
| Database per Service | Each service owns its own database, not shared |
| Polyglot Persistence | Choose the appropriate DB for each service |
| API Composition | Cross-service queries by aggregate API calls |
| CQRS + View | Create read-optimized views for complex queries |
| Data Ownership | Each data has a unique service owner |
| Data Snapshots | Copy the data needed at the time of transaction |
Next article: Event Sourcing & CQRS — Save state as events and separate read/write model.