blog.dopana

Back

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 C
plaintext

Khi 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"
  }
}
json

2. 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)
plaintext

Mỗ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"
  }
}
json

Consumer 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)
plaintext

Lợ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:

BrokerNgôn ngữUse case chính
KafkaScala/JavaEvent streaming, log, throughput siêu cao
RabbitMQErlangMessage queue, routing linh hoạt
NATSGoSiêu nhẹ, cloud native, real-time
Redis Pub/SubCĐơn giản, trong memory
AWS SQS/SNSManagedKhô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-beginning
bash

Saga 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.shipped
plaintext

Nếu một bước fail: phát event compensating để rollback các bước trước.

payment.failed → inventory.reserve.cancelled → order.cancelled
plaintext

Khô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-ResponseEvent-Driven
CouplingTight (biết địa chỉ service)Loose (chỉ biết event type)
ScalabilityTheo requestTheo throughput
Failure isolationMột service chết ảnh hưởng chainEvent buffer, retry
Real-timeWebSocket pollingNative (kafka consumer)
DebugDễ (trace request)Khó (event chain dài)
ComplexityThấpCao
ConsistencyStrong (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.

Tài liệu tham khảo#