Event-Driven Architecture — Khi Request-Response Không Đủ
Event-Driven Architecture giúp hệ thống phản ứng theo thời gian thực, loose coupling, scale ngang. So sánh patterns, message broker, use case.
Hầu hết developer bắt đầu với request-response: client gửi request, server xử lý, trả response. Pattern này đơn giản và hiệu quả — nhưng có giới hạn. Khi hệ thống cần phản ứng với sự kiện theo thời gian thực, kết nối nhiều service mà không hard-code dependency, hoặc xử lý hàng triệu sự kiện đồng thời — Event-Driven Architecture (EDA) là câu trả lời.
EDA Là Gì?#
Event-Driven Architecture là kiến trúc nơi các component giao tiếp với nhau thông qua events (sự kiện), không gọi trực tiếp.
Request-Response: Event-Driven:
Service A → Service B Service A → [Event Bus] → Service B
Service A → [Event Bus] → Service CplaintextKhi một việc xảy ra (order.created, user.registered, payment.failed), service phát ra event. Các service khác lắng nghe và phản ứng — mà không cần biết ai đang lắng nghe.
Ba Thành Phần Chính#
1. Event#
Event là thông báo rằng một việc đã xảy ra. Dạng phổ biến: JSON với event type, timestamp, payload, metadata.
{
"eventType": "order.created",
"timestamp": "2026-07-01T10:00:00Z",
"id": "evt_123",
"data": {
"orderId": "ord_456",
"userId": "usr_789",
"total": 299000,
"items": ["prod_001", "prod_002"]
},
"metadata": {
"version": 1,
"source": "order-service"
}
}json2. Event Producer#
Service phát ra event khi có việc xảy ra. Producer không cần biết ai sẽ xử lý event đó.
3. Event Consumer#
Service lắng nghe event và phản ứng. Consumer không cần biết ai phát ra event.
Ba Pattern Chính#
1. Event Notification#
Pattern đơn giản nhất. Service A gửi event thông báo cho Service B.
order-service ──→ order.created ──→ email-service (gửi email xác nhận)
──→ inventory-service (cập nhật tồn kho)
──→ analytics-service (ghi log)plaintextMỗi service xử lý độc lập. Nếu email-service chết, order không bị ảnh hưởng.
2. Event-Carried State Transfer#
Event chứa đủ dữ liệu để consumer xử lý — không cần query lại producer.
{
"eventType": "user.updated",
"data": {
"userId": "usr_789",
"name": "Nguyen Van A",
"email": "a@example.com"
}
}jsonConsumer lưu vào local database, không cần gọi user-service mỗi lần. Đánh đổi: duplicate data, cần sync.
3. Event Sourcing#
Không lưu trạng thái hiện tại — chỉ lưu chuỗi sự kiện.
[order.created] → [item.added] → [payment.received] → [order.shipped]
↓
Tính toán trạng thái từ events
currentState = replay(events)plaintextLợi ích: audit trail đầy đủ, time travel, debug dễ. Nhược: complex, cần snapshot, learning curve cao.
Message Broker#
Event bus trong EDA thường dùng message broker:
| Broker | Ngôn ngữ | Use case chính |
|---|---|---|
| Kafka | Scala/Java | Event streaming, log, throughput siêu cao |
| RabbitMQ | Erlang | Message queue, routing linh hoạt |
| NATS | Go | Siêu nhẹ, cloud native, real-time |
| Redis Pub/Sub | C | Đơn giản, trong memory |
| AWS SQS/SNS | Managed | Không cần quản lý broker |
Kafka — Thống Trị Event Streaming#
Kafka là lựa chọn phổ biến nhất cho EDA nhờ:
- Throughput: Hàng triệu msg/s
- Persistent: Lưu trên disk, replay được
- Partitioning: Scale ngang
- Exactly-once semantics: Không mất message, không trùng
- Ecosystem: Kafka Connect, Kafka Streams, Schema Registry
# Kafka CLI
kafka-topics --create --topic orders --partitions 3 --replication-factor 2
kafka-console-producer --topic orders --value-serializer json
kafka-console-consumer --topic orders --from-beginningbashfrom kafka import KafkaProducer, KafkaConsumer
# Producer
producer = KafkaProducer(
bootstrap_servers='localhost:9092',
value_serializer=lambda v: json.dumps(v).encode()
)
producer.send('orders', {
'eventType': 'order.created',
'orderId': 'ord_456',
'userId': 'usr_789'
})
# Consumer
consumer = KafkaConsumer(
'orders',
bootstrap_servers='localhost:9092',
group_id='email-service',
value_deserializer=lambda v: json.loads(v.decode())
)
for msg in consumer:
event = msg.value
if event['eventType'] == 'order.created':
send_email(event['userId'])pythonSaga Pattern Trong Microservices#
EDA giải quyết vấn đề distributed transaction trong microservices qua Saga — chuỗi các local transaction, mỗi bước phát event cho bước tiếp theo.
order-service ──→ order.created
↓
payment-service ──→ payment.received
↓
inventory-service ──→ inventory.reserved
↓
shipping-service ──→ order.shippedplaintextNếu một bước fail: phát event compensating để rollback các bước trước.
payment.failed → inventory.reserve.cancelled → order.cancelledplaintextKhông cần distributed transaction (XA, 2PC) — mỗi service tự quản lý transaction của mình.
EDA vs Request-Response#
| Tiêu chí | Request-Response | Event-Driven |
|---|---|---|
| Coupling | Tight (biết địa chỉ service) | Loose (chỉ biết event type) |
| Scalability | Theo request | Theo throughput |
| Failure isolation | Một service chết ảnh hưởng chain | Event buffer, retry |
| Real-time | WebSocket polling | Native (kafka consumer) |
| Debug | Dễ (trace request) | Khó (event chain dài) |
| Complexity | Thấp | Cao |
| Consistency | Strong (transaction) | Eventual |
Anti-Patterns#
1. Schema không tương thích#
Service A thêm field vào event → Service B crash.
Giải pháp: Schema Registry (Avro, Protobuf), backward/forward compatibility.
2. Event quá nhỏ hoặc quá to#
Quá nhỏ: hàng ngàn event cho một use case, network overhead. Quá to: chậm, tốn băng thông.
Giải pháp: thiết kế event size vừa phải, ưu tiên Event-Carried State Transfer.
3. Debug mù mịt#
Event chạy qua 5 service, ai fail?
Giải pháp: correlation ID xuyên suốt, tracing (OpenTelemetry), event log.
Khi Nào Dùng EDA?#
Nên dùng:
- Hệ thống microservices cần loose coupling
- Xử lý real-time (notification, feed, analytics)
- Workflow nhiều bước (order processing, onboarding)
- Cần scale ngang theo throughput
- Audit trail cho tất cả thay đổi
Không nên dùng:
- CRUD đơn giản (dùng REST)
- Cần strong consistency ngay lập tức
- Đội nhóm nhỏ, chưa đủ kinh nghiệm vận hành broker
Kết Luận#
Event-Driven Architecture là bước tiến tự nhiên khi hệ thống lớn hơn và request-response không còn đáp ứng được. Nó cho phép loose coupling, scale ngang, real-time processing, và failure isolation — nhưng kèm độ phức tạp cao hơn.
Bắt đầu với Kafka + một event nhỏ (order.created), dùng Saga cho distributed transaction, thêm Schema Registry khi event nhiều loại, và luôn gắn correlation ID để debug.
Đừng EDA hoá toàn bộ hệ thống ngay từ đầu. Biến một request-response endpoint thành event-driven trước, đo lường, rồi mở rộng.