Event-driven Architect

Dương TiếnDương Tiến
8 min read

EDA/MDA

EDA (Event-Driven Architecture) là kiến trúc phần mềm dựa trên sự kiện (event) để giao tiếp giữa các thành phần hoặc dịch vụ. Các thành phần trong hệ thống không gọi trực tiếp lẫn nhau mà phản ứng với các sự kiện (event) được phát sinh trong hệ thống.

Thay vì sử dụng mô hình request-response như REST API truyền thống, EDA cho phép các dịch vụ hoạt động theo cơ chế publish-subscribe hoặc message queue để xử lý thông tin một cách bất đồng bộ.

Cơ chế hoạt động

EDA bao gồm ba thành phần chính:

  1. Event Producer (Nhà sản xuất sự kiện):

    • Sinh ra sự kiện khi có thay đổi trong hệ thống (ví dụ: người dùng đăng ký tài khoản, đơn hàng được tạo).

    • Gửi sự kiện đến một hệ thống trung gian như Message Broker (Kafka, RabbitMQ, AWS SNS, SQS).

  2. Event Broker (Trung gian sự kiện):

    • Nhận sự kiện từ Producer.

    • Lưu trữ và phát sự kiện đến các thành phần quan tâm (Subscriber).

    • Ví dụ: Apache Kafka, RabbitMQ, NATS, AWS EventBridge.

  3. Event Consumer (Người tiêu thụ sự kiện):

    • Nhận sự kiện từ Event Broker.

    • Thực hiện các tác vụ dựa trên nội dung sự kiện.

    • Ví dụ: Lưu thông tin vào cơ sở dữ liệu, gửi email thông báo.

Ví dụ

  • Khi một đơn hàng được tạo, Order Service phát sự kiện "ORDER_CREATED".

  • Inventory Service lắng nghe sự kiện này và giảm số lượng hàng tồn kho.

  • Notification Service nhận sự kiện và gửi email xác nhận đơn hàng.

Trường hợp sử dụng

EDA phù hợp với các hệ thống cần xử lý dữ liệu theo thời gian thực, khả năng mở rộng cao và giảm độ phụ thuộc giữa các dịch vụ. Một số ứng dụng thực tế:

  1. Thương mại điện tử:

    • Xử lý đơn hàng, kiểm tra tồn kho, gửi thông báo khách hàng theo sự kiện.
  2. Hệ thống thanh toán & ngân hàng:

    • Xử lý giao dịch bất đồng bộ, phát hiện gian lận dựa trên sự kiện giao dịch.
  3. Hệ thống IoT (Internet of Things):

    • Xử lý dữ liệu cảm biến theo thời gian thực, phản hồi khi có sự kiện bất thường.
  4. Mạng xã hội:

    • Đẩy thông báo khi có tương tác mới (like, comment, share)

Cách triển khai

1. Sử dụng Message Broker (Pub/Sub Model)

  • Công nghệ phổ biến: Apache Kafka, RabbitMQ, AWS SNS, Google Pub/Sub.

Ưu điểm:

  • Hỗ trợ bất đồng bộ, giúp hệ thống mở rộng dễ dàng.

  • Các dịch vụ không bị phụ thuộc trực tiếp vào nhau.

Nhược điểm:

  • Cần triển khai hệ thống quản lý tin nhắn trung gian.

  • Phải xử lý vấn đề ordering (thứ tự sự kiện)duplicate event (trùng sự kiện).

2. Sử dụng Event Sourcing

  • Định nghĩa: Lưu trạng thái của hệ thống bằng cách ghi lại toàn bộ sự kiện thay vì trạng thái cuối cùng.

  • Công nghệ phổ biến: Apache Kafka, EventStore, Axon Framework

Ưu điểm:

  • Khả năng phục hồi dữ liệu từ quá khứ (replay event).

  • Hỗ trợ audit log, debugging.

Nhược điểm:

  • Phức tạp hơn so với mô hình dữ liệu truyền thống.

3. Sử dụng Choreography Pattern (Tự động điều phối)

  • Mỗi service tự động phát sự kiện và phản ứng với sự kiện từ các service khác.

Ưu điểm:

  • Giảm độ phụ thuộc vào một service điều phối trung tâm.

Nhược điểm:

  • Khó kiểm soát luồng công việc khi hệ thống mở rộng.

4. Sử dụng Orchestration Pattern (Điều phối tập trung)

  • Dùng một service trung tâm để điều phối các sự kiện, thường là một Workflow Engine như Camunda hoặc Temporal.

Ưu điểm:

  • Dễ quản lý quy trình nghiệp vụ.

Nhược điểm:

  • Gây ra sự phụ thuộc vào service điều phối.

So sánh EDA với mô hình REST

Tiêu chíEvent-Driven Architecture (EDA)REST API truyền thống
Giao tiếpBất đồng bộĐồng bộ (Request-Response)
Khả năng mở rộngDễ mở rộng với nhiều consumerKhó mở rộng khi số lượng request tăng
Độ couplingThấp (các service không gọi nhau trực tiếp)Cao (service gọi trực tiếp lẫn nhau)
Độ trễThấp, phù hợp real-time processingCao hơn do phải chờ phản hồi
Phức tạp triển khaiPhức tạp hơn do cần message brokerDễ triển khai hơn

Ví dụ để so sánh EDA và REST

Ví dụ có 2 service licensing-service và organization-service, licensing-service gọi sang organization-service để lấy thông tin. Nếu lần nào cũng gọi trực tiếp sang organization-service không phải là một cách hay bởi vì thời gian phản hồi có thể sẽ lớn, lý do là độ trễ mạng giữa 2 service và tại organization-service có thể sẽ phải gọi vào DB.

Để giải quyết vấn đề này có thể sử dụng cached data tại licensing-service. Nếu theo hướng tiếp cận cached data thì có một số vấn đề cần chú ý:

  • Làm sao để dữ liệu cache nhất quán trên tất cả các instance của licensing-service?

Điều này có nghĩa là không thể cache dữ liệu tại local của từng instance và cũng không thể cache dữ liệu tại server hosting licensing-service vì các instance service có thể được scale trên một cluster server gồm nhiều server khác nhau. Phương án được đưa ra là sử dụng Redis cluster.

  • Khi dữ liệu bên trong organization-service thay đổi (delete/update) thì làm sao để invalid cache?

Điều này có nghĩa là organization-service phải thông báo cho licensing-service khi có sự thay đổi. 3 phương án được đưa ra là sử dụng REST hoặc EDA để thông báo, hoặc cho phép organization-service truy cập trực tiếp vào Redis cluster (cách này là anti-pattern trong thiết kế microservice khi cố gắng truy cập DB đã được quy hoạch business logic cho licensing-service).

Hai cách tiếp cận được xem xét sử dụng cho vấn đề này. Thứ nhất là licensing-service và organization-service giao tiếp trao đổi thông tin qua synchronous request-response (giao tiếp đồng bộ). Cách thứ hai, khi có sự thay đổi dữ liệu organization-service sẽ publish một sự kiện (event driven) thông báo việc dữ liệu thay đổi cho licensing-service (giao tiếp không đồng bộ).

Giao tiếp đồng bộ request-response

Trong cách tiếp cận này licensing-service sẽ kiểm tra trong cache - Redis cluster xem có dữ liệu hay không, nếu không có dữ liệu trong cache thì sẽ gọi sang organization service để lấy dữ liệu, sau đó sẽ lưu vào cache trước khi trả dữ liệu cho người dùng.

Nếu organization-service có sự thay đổi dữ liệu sẽ call REST endpoint mà licensing service expose để thông báo dữ liệu thay đổi, cần cập nhật hoặc bổ sung dữ liệu trong cache.

Trong cách tiếp cận này có một số vấn đề:

  1. Organization-service và licensing-service đang bị phụ thuộc chặt chẽ vào nhau (high/tight coupling)

  2. Nếu endpoint mà licensing-service expose để organization-service callback thay đổi dữ liệu thì code trong organization-service cũng bị thay đổi.

  3. Nếu có một microservice khác cũng cần lấy dữ liệu từ organization giống như licensing service, cũng quan tâm việc organization service có sự thay đổi dữ liệu thì code trong organization-service phải thay đổi để thực hiện gọi một api khác để thông báo dữ liệu thay đổi giống như licensing-service expose api cho organization-service.

Giao tiếp không đồng bộ

Trong cách tiếp cận này thì khi organization-service có thay đổi sẽ gửi một message/event vào queue. Licensing-service sẽ lắng nghe các thay đổi này trên queue để update thông tin trên Redis.

Cách tiếp cận này có một số ưu điểm là :

Loose coupling

Licensing-service chỉ cần subcribe topic cụ thể để lấy bản tin/sự kiện (message) mà nó quan tâm, không cần quan organization đẩy message đó lên topic bằng cách nào.

Durability

Message khi đẩy vào queue sẽ được giữ lại trong queue một khoảng thời gian, do đó gia tăng cơ hội licensing-service đọc được message sau khi downtime và được khởi động lại.

Scalability

Khi service gửi message đến queue nó sẽ giải phóng tài nguyên và tiếp tục công việc. Tương tự service tiêu thụ (consume) message không thể xử lý đủ nhanh thì việc tạo thêm các consumer trong service tiêu thụ message cũng đơn giản hơn rất nhiều. Cơ chế này giúp dễ dàng mở rộng bằng cách tăng số lượng các instance producer và consumer.

Flexibility (Linh hoạt)

Service gửi (producer) và service tiêu thụ (consumer) message không cần biết về nhau, chúng chỉ quan tâm việc gửi và nhận message ở topic nào. Việc thêm producer mới xuất bản cùng loại message và thêm consumer mới quan tâm đến cùng loại message trở nên dễ dàng.

Tradeoff

Phức tạp hơn trong quá trình triển khai vận hành. Một số vấn đề cần quan tâm như:

- Việc gửi, nhận và xử lý tin nhắn cần theo thứ tự hay không?

- Nếu gửi, nhận tin nhắn thất bại thì cơ chế retry, handle như thế nào?

- Vì việc gửi, nhận tin nhắn diễn ra bất đồng bộ nên cần cơ chế để debug gỡ rối, tracing (sử dụng correlation ID) để theo dõi luồng xử lý.

Recap

Thế tóm lại là khi nào sử dụng cái nào?

Nên sử dụng EDA:

  • Khi cần xử lý nhiều tác vụ cùng lúc mà không gây nghẽn hệ thống.

  • Khi cần khả năng mở rộng lớn và yêu cầu phản hồi theo thời gian thực.

Không nên sử dụng EDA

  • Khi ứng dụng không yêu cầu tính bất đồng bộ cao.

  • Khi hệ thống đơn giản và có thể hoạt động tốt với REST API truyền thống.

Kiến trúc Event-Driven Architecture (EDA) giúp hệ thống Microservices hoạt động linh hoạt, giảm độ phụ thuộc giữa các dịch vụ và cải thiện hiệu suất bằng cách xử lý sự kiện bất đồng bộ. Tuy nhiên nên kết hợp cả REST API và EDA để tối ưu hiệu suất và khả năng mở rộng.

0
Subscribe to my newsletter

Read articles from Dương Tiến directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dương Tiến
Dương Tiến