blog.dopana

Back

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#

MonolithMicroservices
Triển khai1 deploy = cả appMỗi service deploy riêng
ScaleScale toàn bộScale từng service
Độ phức tạpThấp (lúc đầu)Cao (luôn luôn)
Lỗi1 lỗi có thể crash cả appCô lập trong service
Team1 teamNhiề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, SMS
txt

Mỗ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:3000
yaml

Kubernetes 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-service
txt

Gateway 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 });
});
typescript

Database 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();
});
typescript

Cô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-alpine
yaml

Kubernetes — Cho Production#

Anti-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.

Tài liệu tham khảo#