Microservices Architecture — Khi Nào Và Làm Thế Nào?
Tìm hiểu kiến trúc microservices: ưu nhược điểm, cách phân chia service, giao tiếp giữa các service và triển khai.
Microservices không phải “viên đạn bạc” — nó là kiến trúc đi kèm chi phí. Bài này giúp bạn quyết định khi nào nên dùng và triển khai thế nào.
Monolith vs Microservices#
| Monolith | Microservices | |
|---|---|---|
| Triển khai | 1 deploy = cả app | Mỗi service deploy riêng |
| Scale | Scale toàn bộ | Scale từng service |
| Độ phức tạp | Thấp (lúc đầu) | Cao (luôn luôn) |
| Lỗi | 1 lỗi có thể crash cả app | Cô lập trong service |
| Team | 1 team | Nhiều team độc lập |
Khi Nào Nên Dùng Microservices?#
Nên bắt đầu với Monolith#
Hầu hết project nên bắt đầu với monolith. Đừng microservices từ ngày 0.
Chuyển Sang Microservices Khi#
- Team > 10 người, nhiều team riêng
- Deploy chậm vì phải chờ nhau
- Một module cần scale khác biệt (ví dụ: notification service cần nhiều resource)
- Cần công nghệ khác nhau cho từng module (Python cho ML, Node cho API)
Phân Chia Service#
Theo Domain (DDD — Domain-Driven Design)#
users-service → Quản lý user, auth
orders-service → Đơn hàng, thanh toán
products-service → Sản phẩm, kho hàng
notifications-service → Email, push, SMStxtMỗi service sở hữu data riêng — không share database.
Kích Thước Service#
Service không nên quá nhỏ (nanoservices) hay quá lớn (distributed monolith). Dấu hiệu service quá to:
-
20 bảng trong database
-
10 developer cùng làm
- Mất > 30 phút để hiểu codebase
Giao Tiếp Giữa Các Service#
Synchronous — HTTP/REST hoặc gRPC#
// Order service gọi User service
const user = await fetch(`http://users-service/users/${userId}`);typescriptƯu: Đơn giản, dễ debug. Nhược: Tăng latency, service phụ thuộc nhau.
Asynchronous — Message Queue#
// Order service publish event
await broker.publish('order.created', {
orderId: '123',
userId: '456',
amount: 100,
});
// Email service consume event
broker.subscribe('order.created', async (event) => {
await sendEmail(event.userId, 'Order confirmed!');
});typescriptƯu: Loose coupling, chịu lỗi tốt. Nhược: Phức tạp hơn, khó debug.
Công cụ: RabbitMQ, Kafka, NATS, Redis Pub/Sub.
Service Discovery#
# docker-compose.yml
services:
users-service:
image: users-service
ports:
- "3001:3000"
orders-service:
image: orders-service
environment:
- USERS_SERVICE_URL=http://users-service:3000yamlKubernetes dùng DNS built-in: users-service.namespace.svc.cluster.local.
API Gateway#
Gateway là entry point duy nhất cho client:
Client → API Gateway → users-service
→ orders-service
→ products-servicetxtGateway làm:
- Routing — chuyển request đến service đúng
- Auth — kiểm tra token trước khi vào internal
- Rate limit — bảo vệ hệ thống
- Response aggregation — gộp data từ nhiều service
// Ví dụ với Express Gateway hoặc custom
app.get('/order/:id', async (req, res) => {
const [order, user, product] = await Promise.all([
fetch(`http://orders-service/orders/${req.params.id}`),
fetch(`http://users-service/users/${order.userId}`),
fetch(`http://products-service/products/${order.productId}`),
]);
res.json({ order, user, product });
});typescriptDatabase Per Service#
Mỗi service có database riêng — không share trực tiếp:
- orders-service: PostgreSQL
- users-service: PostgreSQL
- products-service: MongoDB
- analytics-service: ClickHouse
Vấn đề: Query xuyên service? Dùng eventual consistency + CQRS.
Distributed Tracing#
Microservices khó debug — trace giúp theo dõi request xuyên service:
// OpenTelemetry
import opentelemetry from '@opentelemetry/api';
const tracer = opentelemetry.trace.getTracer('orders-service');
app.get('/order/:id', async (req, res) => {
const span = tracer.startSpan('get-order');
span.setAttribute('order.id', req.params.id);
// ... xử lý
span.end();
});typescriptCông cụ: Jaeger, Zipkin, Grafana Tempo, Datadog APM.
Triển Khai#
Docker Compose — Cho Dev#
services:
gateway:
build: ./gateway
ports:
- "8080:8080"
users:
build: ./users-service
orders:
build: ./orders-service
rabbitmq:
image: rabbitmq:4-alpineyamlKubernetes — Cho Production#
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-service
spec:
replicas: 3
selector:
matchLabels:
app: users-service
template:
metadata:
labels:
app: users-service
spec:
containers:
- name: users
image: myapp/users-service:latest
ports:
- containerPort: 3000yamlAnti-patterns Cần Tránh#
1. Shared Database#
Service A đọc thẳng database của Service B → mất independence, khó thay đổi schema.
2. Synchronous Chain#
Service A → Service B → Service C → Service D
Một service chậm → cả chain chậm → timeout → mất request.
Giải pháp: Dùng async, circuit breaker, hoặc gộp response tại gateway.
3. Over-engineering#
3 developer, 10 user — mà 5 services. Bắt đầu đơn giản, tách sau khi cần.
Kết Luận#
Microservices giải quyết vấn đề scale của team và tổ chức — không phải vấn đề kỹ thuật thuần túy. Nếu team bạn nhỏ, monolith là lựa chọn đúng đắn. Khi đau đến mức “phải tách”, hãy tách — đừng tách trước khi đau.