Bài blog
Event-Driven Architecture: Đánh Đổi Bạn Cần Chấp Nhận Trước Khi Cam Kết
Event-driven architecture tách ghép service và cho phép mở rộng, nhưng nó chuyển độ phức tạp từ runtime coupling sang data consistency và observability vận hành.
- Danh mục
- architecture
- Xuất bản
Hai Thứ Khác Nhau Được Gọi Là Event-Driven
Event notification: service phát ra event để báo hiệu rằng điều gì đó đã xảy ra. Service khác subscribe và phản ứng. Không ai biết ai đang lắng nghe.
Event sourcing: trạng thái của một entity được dẫn xuất hoàn toàn từ lịch sử event của nó. Thay vì lưu trạng thái hiện tại, bạn lưu mọi event dẫn đến nó.
Hai cái này thường bị nhầm lẫn. Bạn có thể dùng event notification mà không cần event sourcing. Hãy chọn chúng độc lập.
Event Notification: Lợi Ích và Bẫy
Lợi ích:
- Service được tách ghép. Order service không biết inventory, email, và analytics service tồn tại.
- Consumer mới có thể subscribe mà không cần sửa producer.
- Log audit tự nhiên.
- Tách ghép thời gian — consumer xử lý theo nhịp của mình.
Bẫy:
Schema coupling. Tách ghép về runtime, nhưng gắn chặt ở schema level. Producer thay đổi tên field event phá vỡ mọi consumer.
Không có transaction boundary. Event được emit sau database write, nhưng write và emit không nguyên tử. Service có thể crash ở giữa. Đây là nơi outbox pattern xuất hiện.
Outbox Pattern
Ghi event vào bảng outbox trong cùng database transaction với business operation:
BEGIN;
UPDATE orders SET status = 'confirmed' WHERE id = ?;
INSERT INTO outbox (event_type, payload, created_at)
VALUES ('OrderConfirmed', '{"order_id": 123}', NOW());
COMMIT;
Outbox relay publish OrderConfirmed lên Kafka và đánh dấu row là đã gửi. Ngay cả khi service crash sau database commit, event không bị mất.
Điều này đảm bảo at-least-once delivery. Consumer phải idempotent: xử lý cùng event hai lần phải cho kết quả giống với xử lý một lần.
Event Sourcing: Khi Nào Đáng Dùng
Event sourcing là lựa chọn đúng khi:
- Lịch sử audit là yêu cầu business, không phải nice-to-have. Giao dịch tài chính, hồ sơ y tế, domain có quy định.
- Temporal query cần thiết. "Trạng thái tài khoản này vào lúc 3 giờ chiều thứ Ba tuần trước là gì?"
- Event replay có giá trị. Bạn có thể rebuild projection.
Chi phí: bạn phải duy trì event store, quản lý schema evolution cho event có thể cần replay nhiều năm sau, xử lý snapshot strategy.
Saga Pattern cho Distributed Transaction
Khi business operation trải dài nhiều service, bạn không thể dùng database transaction. Saga pattern điều phối các bước như chuỗi local transaction:
OrderCreated → InventoryReserved → PaymentCharged → OrderDispatched
↓ (payment thất bại)
InventoryReleased → OrderCancelled
Choreography saga: mỗi service lắng nghe event và emit event tiếp theo. Đơn giản để triển khai, khó hiểu khi phát triển.
Orchestration saga: saga orchestrator gửi command đến service và chờ response. Luồng rõ ràng và dễ đọc. Được ưu tiên cho saga phức tạp, long-running.
Observability Không Phải Tùy Chọn
Hệ thống event-driven yêu cầu distributed tracing. Đầu tư vào:
- Trace propagation: truyền correlation ID trong mọi event header.
- Dead letter queue: event thất bại phải đi đến đâu đó có thể nhìn thấy.
- Consumer lag monitoring: Kafka consumer group lag cho bạn biết consumer có bị tụt hậu không.
Quyết Định
Chọn event-driven architecture khi service có yêu cầu vận hành thực sự khác nhau và lợi ích tách ghép biện minh cho chi phí observability.
Tránh nó khi bạn đang tối ưu hóa sớm một monolith chưa đạt giới hạn. Chi phí vận hành của hệ thống event-driven là thật — hãy bắt đầu đơn giản và migrate các bounded context cụ thể sang event khi coupling trở thành vấn đề delivery.