Outbox Pattern 101

Hi anh em, lại là mình đây

Hôm nay mình muốn thử nghiệm một chút content mới về microservice / system design. Bắt đầu sẽ là một Pattern đơn giản nhưng thường xuyên xuất hiện khi các bạn làm việc với Microservice, đó là Outbox Pattern.

1. GIỚI THIỆU VỀ Outbox Pattern

Trong các hệ thống phân tán (hay distributed system dưới dạng microservices), tình huống thường thấy là bạn sẽ cần write vào database của một service, sau đó bắn một event (sự kiện, hoặc còn gọi là message) lên hệ thống message broker (phổ biến là RabbitMQ, Kafka) để các service khác nhận và tiếp tục xử lý. Trong tình huống này, một trong hai hành động trên có thể thất bại. Ví dụ bạn chưa write thành công vào DB, nhưng message đã được gửi đi tới service khác, có thể gây sai lệch dữ liệu. Outbox Pattern là một hướng handle đơn giản cho vấn đề này.

2. CÁCH Outbox Pattern HOẠT ĐỘNG
  1. Bước 1: Trong transaction sẽ bao gồm hai hành động: ghi dữ liệu mới vào bảng chính (ví dụ: orders) và đồng thời lưu thông tin cần gửi (message) vào một bảng outbox trong cơ sở dữ liệu. Vì cùng một transaction, điều này đảm bảo tính ACID: hoặc là bạn ghi thành công cả 2 thao tác, hoặc là bạn không có gì cả.
  2. Bước 2: Sau khi transaction hoàn thành, một tiến trình riêng biệt sẽ đọc các bản ghi từ bảng outbox, gửi thông tin này tới message broker, sau đó đánh dấu bản ghi là đã được xử lý. Bạn sẽ thấy nó rất tương đồng như queue job database trên Laravel phải không. Tiến trình này có thể được triển khai như một cronjob, hoặc một deamon job lắng nghe các sự kiện thay đổi trực tiếp từ bảng outbox(CDC hay Change Data Capture).
3. Lợi ích của Outbox Pattern
  • Tính nhất quán: Đảm bảo tính nhất quán giữa cơ sở dữ liệu và hệ thống message broker. Vì sử dụng transaction giữa thao tác cập nhật vào db và ghi vào outbox, chúng ta yên tâm sự nhất quán theo nguyên tắc ACID.
  • Khả năng chịu lỗi: Nếu message broker gặp sự cố, hệ thống vẫn lưu trữ được các message chưa gửi trong bảng outbox và gửi chúng sau khi sự cố được khắc phục.
  • Mở rộng dễ dàngOutbox Pattern giúp dễ dàng mở rộng hệ thống với các microservices khác mà không làm gián đoạn hệ thống cốt lõi. Mình chỉ cần sửa một chút tiến trình quét bảng outbox để nó bắn thêm sang service khác khi cần tích hợp thêm service mới, sẽ đơn giản hơn và hạn chế tác động vào source code cũ.

Để dễ hình dung, mời ae tham khảo một ví dụ cực kỳ đơn giản dưới đây nhé

Bảng outbox có các cột:
1. event_type: Loại sự kiện (ví dụ: order_created, user_registered, v.v.).
2. payload: Nội dung chi tiết của sự kiện, được lưu dưới dạng JSON.
3. status: Trạng thái của message, có thể là pending (chờ xử lý) hoặc processed (đã gửi).
(Các bạn có thể bổ sung index và các luồng xoá bớt message outbox để performance được tốt hơn)

Khi thực hiện một hành động (ví dụ: tạo đơn hàng), bạn sẽ sử dụng transaction để đảm bảo rằng nếu việc ghi dữ liệu vào cả bảng orders và outbox không thành công, thì toàn bộ transaction sẽ bị hủy bỏ.

Sau đó bạn cần tạo một tiến trình (job hoặc command) để đọc các message trong bảng outbox có trạng thái pending, gửi chúng tới message broker, và sau đó cập nhật trạng thái của chúng thành processed. Cronjob hoặc phương án CDC (Change Data Capture) là 2 cách hữu dụng phổ biến trong trường hợp này.

Ở đây, để đơn giản mình ví dụ qua Laravel Task Scheduler, job sẽ chạy 1 phút 1 lần.