🏗️ Bài 5: Kiến Trúc Event-Driven
🎯 Sau bài học này, bạn sẽ:
- Hiểu Event-Driven Architecture (EDA) là gì
- Biết cách Microservices giao tiếp qua Message Queue
- Nắm các pattern: CQRS, Event Sourcing, Saga
- Xem ví dụ kiến trúc e-commerce thực tế
1. Event-Driven Architecture (EDA) Là Gì?
Event-Driven Architecture là kiến trúc mà các thành phần giao tiếp với nhau thông qua events (sự kiện). Khi một sự kiện xảy ra (VD: user đăng ký), nó được publish lên event bus, và các service quan tâm sẽ react (phản ứng) với event đó.
Kiến trúc truyền thống (Request-Driven):
Client → API Gateway → Service A → Service B → Service C
(đợi B xong) (đợi C xong)
→ Chậm, tight coupling
Kiến trúc Event-Driven:
Client → API Gateway → Service A ──publish──▶ Event Bus
│
Service B ◀─subscribe──┘
Service C ◀─subscribe──┘
→ Nhanh, loose coupling, scalable
2. Microservices + Message Queue
Trong kiến trúc microservices, Message Queue đóng vai trò giao tiếp giữa các service:
┌──────────────────────────────────────────────────────────┐
│ E-Commerce System │
│ │
│ ┌─────────┐ order.created ┌────────────┐ │
│ │ Order │───────────────▶│ Inventory │ │
│ │ Service │ │ Service │ │
│ └────┬─────┘ └────────────┘ │
│ │ │
│ │ order.created ┌────────────┐ │
│ ├─────────────────▶│ Payment │ │
│ │ │ Service │ │
│ │ └────────────┘ │
│ │ │
│ │ order.created ┌────────────┐ │
│ └─────────────────▶│ Email │ │
│ │ Service │ │
│ └────────────┘ │
│ ▲ │
│ │ ┌──────────────┐ │
│ └───────────│ Message Queue │ │
│ │ (RabbitMQ / │ │
│ │ Kafka) │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────┘
// Order Service - Publisher
app.post('/orders', async (req, res) => {
const order = await Order.create(req.body);
// Publish event cho các service khác
await eventBus.publish('order.created', {
orderId: order.id,
userId: order.userId,
items: order.items,
total: order.total,
timestamp: new Date()
});
res.status(201).json({ orderId: order.id });
});
// Inventory Service - Subscriber
eventBus.subscribe('order.created', async (event) => {
for (const item of event.items) {
await Inventory.decrementStock(item.productId, item.quantity);
}
await eventBus.publish('inventory.reserved', {
orderId: event.orderId
});
});
// Payment Service - Subscriber
eventBus.subscribe('order.created', async (event) => {
const payment = await processPayment(event.userId, event.total);
await eventBus.publish('payment.completed', {
orderId: event.orderId,
paymentId: payment.id
});
});
3. CQRS (Command Query Responsibility Segregation)
CQRS tách biệt thao tác ghi (Command) và đọc (Query) thành 2 model riêng biệt. Message Queue đồng bộ dữ liệu giữa Read và Write model.
┌─────────┐ Command ┌────────────┐ Event ┌─────────────┐
│ Client ├──────────▶│ Write Model├────────▶│ Event Store │
└────┬────┘ │ (PostgreSQL)│ │ (Kafka) │
│ └────────────┘ └──────┬──────┘
│ │
│ Query ┌────────────┐ ◀── Projection ◀────┘
└────────▶│ Read Model │
│ (Redis/ES) │ ← Optimized cho đọc
└────────────┘
• Hệ thống có tỷ lệ đọc/ghi chênh lệch nhiều (1000 reads : 1 write)
• Cần optimize read performance riêng biệt
• Complex business logic khi ghi
4. Saga Pattern
Saga quản lý distributed transaction qua nhiều service. Thay vì 2-phase commit (chậm), Saga dùng chuỗi local transaction + compensating transaction nếu có lỗi.
Choreography Saga (event-driven):
Order Created ──▶ Inventory Reserved ──▶ Payment Processed ──▶ Order Confirmed
│ │ │
│ (Nếu lỗi) (Nếu lỗi)
│ ▼ ▼
│ Inventory Released Payment Refunded
└──────────▶ Order Cancelled
// Saga: Xử lý đơn hàng
// Step 1: Order Service
eventBus.subscribe('order.created', async (event) => {
await eventBus.publish('inventory.reserve', event);
});
// Step 2: Inventory Service
eventBus.subscribe('inventory.reserve', async (event) => {
try {
await reserveStock(event.items);
await eventBus.publish('inventory.reserved', event);
} catch (err) {
// Compensate: cancel order
await eventBus.publish('order.cancel', {
orderId: event.orderId,
reason: 'Out of stock'
});
}
});
// Step 3: Payment Service
eventBus.subscribe('inventory.reserved', async (event) => {
try {
await processPayment(event);
await eventBus.publish('payment.completed', event);
} catch (err) {
// Compensate: release inventory
await eventBus.publish('inventory.release', event);
await eventBus.publish('order.cancel', {
orderId: event.orderId,
reason: 'Payment failed'
});
}
});
• Phải xử lý idempotency (message có thể bị gửi lại)
• Cần thiết kế compensating action cho mỗi step
• Debug khó hơn so với monolith
5. Event Sourcing
Thay vì lưu trạng thái hiện tại, Event Sourcing lưu tất cả events đã xảy ra. Trạng thái hiện tại được tính bằng cách replay tất cả events.
// Event Store (ví dụ với Kafka)
const events = [
{ type: 'AccountCreated', data: { id: 1, balance: 0 } },
{ type: 'MoneyDeposited', data: { amount: 1000 } },
{ type: 'MoneyWithdrawn', data: { amount: 300 } },
{ type: 'MoneyDeposited', data: { amount: 500 } },
];
// Replay events → tính state hiện tại
function replay(events) {
return events.reduce((state, event) => {
switch (event.type) {
case 'AccountCreated':
return { ...state, ...event.data };
case 'MoneyDeposited':
return { ...state, balance: state.balance + event.data.amount };
case 'MoneyWithdrawn':
return { ...state, balance: state.balance - event.data.amount };
default:
return state;
}
}, {});
}
console.log(replay(events));
// { id: 1, balance: 1200 } (0 + 1000 - 300 + 500)
📝 Tóm Tắt Bài Học
- EDA: Kiến trúc giao tiếp qua events, loose coupling
- Microservices + MQ: Publish event khi state thay đổi, services subscribe để react
- CQRS: Tách read/write model, MQ đồng bộ dữ liệu
- Saga: Distributed transaction qua chuỗi events + compensating actions
- Event Sourcing: Lưu events thay vì state, replay để tính trạng thái