Chào bạn,
Trong bài học trước, chúng ta đã tìm hiểu cách xử lý lỗi một cách hiệu quả trong JavaDelegate. Bạn đã học cách phân biệt giữa lỗi kỹ thuật (technical errors) và lỗi nghiệp vụ (business errors), sử dụng Asynchronous Before để tạo incidents và BpmnError để điều hướng quy trình theo các kịch bản đã định. Toàn bộ logic này đều diễn ra bên trong một JavaDelegate, một lớp Java được process engine trực tiếp "đẩy" (push) công việc vào và thực thi.
Cách tiếp cận này rất mạnh mẽ khi engine được nhúng (embedded) và bạn làm việc trong một hệ sinh thái Java thuần nhất. Tuy nhiên, trong thế giới kiến trúc microservices hiện đại, các dịch vụ thường được phát triển bằng nhiều ngôn ngữ khác nhau, được triển khai và mở rộng một cách độc lập. Việc buộc mọi logic nghiệp vụ phải là một lớp Java được triển khai cùng với engine sẽ tạo ra sự phụ thuộc không mong muốn.
Đây là lúc một mẫu thiết kế khác, mạnh mẽ và linh hoạt hơn, xuất hiện. Trong bài học hôm nay, chúng ta sẽ khám phá External Task Pattern. Mục tiêu của bài học này là giải thích External Task pattern và lợi ích của nó trong kiến trúc microservices. Bạn sẽ hiểu tại sao đây là mẫu hình được khuyến nghị cho hầu hết các trường hợp sử dụng Camunda hiện đại và là nền tảng cho việc điều phối microservices (microservices orchestration).
1. Từ "Push" đến "Pull": Hai Mô hình Tích hợp
Để hiểu External Task Pattern, trước tiên hãy phân biệt rõ hai mô hình tích hợp logic nghiệp vụ với Camunda.
-
Mô hình "Push" (Đẩy): Đây là những gì bạn đã làm với
JavaDelegate.- Process engine chủ động đẩy công việc đến code của bạn.
- Khi quy trình đến một Service Task, engine sẽ gọi trực tiếp một phương thức trong một lớp Java (
executetrongJavaDelegate). - Nhược điểm: Code và engine bị liên kết chặt chẽ. Code phải là Java, phải được đóng gói và triển khai cùng với ứng dụng chứa engine.
-
Mô hình "Pull" (Kéo): Đây chính là cơ chế của External Task Pattern.
- Process engine không thực thi bất kỳ code nào. Thay vào đó, nó tạo ra một mục công việc (work item) và chờ đợi.
- Các ứng dụng bên ngoài, được gọi là Workers, chủ động kéo công việc từ engine.
- Ưu điểm: Engine và worker được tách rời hoàn toàn.
Tài liệu sau đây của Camunda giới thiệu ngắn gọn về hai mẫu hình này.
Invoking services from a Camunda 7 process
Để bắt đầu, hãy đọc phần giới thiệu ngắn về hai mô hình "Push" và "Pull" trong tài liệu chính thức của Camunda. Điều này sẽ giúp bạn có cái nhìn tổng quan ban đầu.
Hãy đọc phần đầu tiên có tiêu đề "Understanding the possibilities", tập trung vào hai đoạn "Push and pull" và "External tasks".
2. Giải phẫu External Task Pattern
Vậy mô hình "Pull" này hoạt động cụ thể như thế nào? Nó dựa trên một vài khái niệm chính:
-
Service Task (với Topic): Trong mô hình BPMN, bạn cấu hình một Service Task với loại
ImplementationlàExternal. Thay vì chỉ định một Java class, bạn sẽ chỉ định một Topic.Topiclà một chuỗi ký tự (ví dụ:"charge-credit-card","prepare-shipment") định danh cho loại công việc cần được thực hiện. Nó hoạt động giống như tên của một hàng đợi (queue). -
External Worker: Đây là một ứng dụng hoặc microservice hoàn toàn độc lập. Nó có thể được viết bằng bất kỳ ngôn ngữ nào (Java, Python, Node.js, C#,...). Nhiệm vụ của Worker là thực thi logic nghiệp vụ tương ứng với một
Topicnào đó. -
Chu trình Fetch & Lock:
- Khi một quy trình chạy đến Service Task được cấu hình
External, engine sẽ tạo một external task instance và dừng lại ở đó. - Worker của bạn sẽ định kỳ hỏi (poll) engine thông qua REST API: "Có công việc nào thuộc topic
[tên-topic]không?". - Nếu có, engine sẽ giao công việc đó cho worker và khóa (lock) nó lại trong một khoảng thời gian nhất định. Việc khóa này đảm bảo rằng không có worker nào khác có thể lấy cùng một công việc.
- Worker nhận công việc, thực thi logic nghiệp vụ của mình (ví dụ: gọi một API khác, xử lý dữ liệu).
- Sau khi hoàn thành, worker sẽ gọi lại REST API của engine để báo cáo rằng công việc đã hoàn thành (complete). Engine sẽ tiếp tục luồng quy trình.
- Khi một quy trình chạy đến Service Task được cấu hình
Video sau đây giải thích rất trực quan về khái niệm này và chu trình hoạt động của nó.
Video này sẽ so sánh External Task với các loại task khác và giải thích chi tiết về chu trình "fetch and lock" - xương sống của pattern này.
Hãy xem từ đầu đến 07:38. Chú ý cách video so sánh External Task với User Task (một danh sách công việc cho người dùng) và Service Task (một danh sách công việc cho máy móc), và cách nó mô tả luồng giao tiếp giữa engine và worker.
Sơ đồ dưới đây tóm tắt luồng tương tác giữa các thành phần:
3. Lợi ích trong Kiến trúc Microservices
Sự tách rời giữa engine và worker mang lại những lợi ích to lớn, đặc biệt là trong môi trường microservices. Đây là lý do tại sao External Task Pattern lại là lựa chọn hàng đầu.
Để có danh sách đầy đủ, hãy đọc phần tiếp theo trong tài liệu của Camunda.
Invoking services from a Camunda 7 process
Tài liệu này liệt kê rất rõ ràng các lợi ích chính của việc sử dụng External Tasks.
Đọc phần "Using external tasks comes with the following advantages" và phần "General recommendation". Hãy chú ý đến từng lợi ích được liệt kê.
Bây giờ, hãy cùng phân tích sâu hơn những lợi ích quan trọng nhất:
-
Polyglot Architectures (Kiến trúc đa ngôn ngữ): Vì giao tiếp giữa engine và worker diễn ra qua REST API, worker có thể được viết bằng bất kỳ ngôn ngữ nào. Team Java có thể viết worker bằng Java, team Python có thể viết worker bằng Python. Điều này hoàn toàn phù hợp với triết lý của microservices, nơi mỗi dịch vụ có thể chọn công nghệ phù hợp nhất.
-
Independent Scalability (Khả năng mở rộng độc lập): Nếu tác vụ "xử lý thanh toán" trở thành một điểm nghẽn, bạn chỉ cần triển khai thêm các thực thể (instances) của worker xử lý thanh toán. Bạn không cần phải đụng đến engine hay các worker khác. Điều này cho phép bạn tối ưu hóa tài nguyên một cách hiệu quả.
-
Resilience & Failure Isolation (Linh hoạt và Cách ly lỗi): Nếu một worker bị lỗi và sập, nó không ảnh hưởng đến process engine. Task mà nó đang xử lý sẽ bị hết thời gian khóa (lock timeout) và sẽ trở lại hàng đợi để một worker khác có thể xử lý. Điều này giúp cô lập lỗi trong phạm vi một microservice và tăng tính bền vững của toàn bộ hệ thống.
-
Temporal Decoupling (Tách rời về mặt thời gian): Worker không cần phải hoạt động cùng lúc khi task được tạo ra. Nếu tất cả các worker cho một topic nào đó đang bận hoặc tạm thời không khả dụng, các task sẽ chỉ đơn giản là đợi trong engine. Điều này loại bỏ sự cần thiết của một message queue trung gian chỉ để đảm bảo việc giao nhận công việc.
-
Deployment Independence (Triển khai độc lập): Bạn có thể cập nhật và triển khai một phiên bản mới của một worker (microservice) mà không cần phải dừng hay triển khai lại process engine hoặc các dịch vụ khác.
Sơ đồ kiến trúc sau đây cho thấy một ví dụ thực tế:

4. Bức tranh lớn: Orchestration vs. Choreography
Trong thế giới microservices, có hai mẫu hình chính để các dịch vụ giao tiếp với nhau:
-
Choreography (Vũ đạo): Các dịch vụ giao tiếp bằng cách phát và lắng nghe các sự kiện (events). Ví dụ, dịch vụ
Orderphát ra sự kiện "Order Placed". Dịch vụPaymentlắng nghe sự kiện đó và xử lý thanh toán, sau đó lại phát ra sự kiện "Payment Processed".- Vấn đề: Không có một cái nhìn tổng thể về luồng xử lý end-to-end. Khi có lỗi, việc truy vết và gỡ lỗi trở nên rất khó khăn. Thay đổi luồng nghiệp vụ (ví dụ: thêm bước kiểm tra kho) đòi hỏi phải thay đổi nhiều dịch vụ.
-
Orchestration (Điều phối): Có một thành phần trung tâm (orchestrator) chịu trách nhiệm điều khiển luồng công việc. Thành phần này ra lệnh cho các dịch vụ khác phải làm gì và theo thứ tự nào.
- Lợi ích: Cung cấp khả năng quan sát (visibility) toàn bộ quy trình. Luồng nghiệp vụ được định nghĩa rõ ràng ở một nơi duy nhất (sơ đồ BPMN). Việc theo dõi, gỡ lỗi và thay đổi luồng trở nên đơn giản hơn rất nhiều.
Camunda với External Task Pattern chính là một giải pháp Orchestration hoàn hảo cho microservices. Nó đóng vai trò là "nhạc trưởng", điều phối các "nhạc công" (microservices) để tạo ra một bản giao hưởng nghiệp vụ hoàn chỉnh, mà không cần phải can thiệp vào cách mỗi nhạc công chơi nhạc cụ của mình.
Video sau đây từ một hội thảo của Camunda sẽ làm rõ hơn sự khác biệt này và vai trò của workflow engine.
CamundaCon 2018: The Role of Workflows in Microservices (Camunda)
Bernd Rücker, đồng sáng lập Camunda, giải thích những thách thức của kiến trúc chỉ dựa trên sự kiện (choreography) và tại sao orchestration lại là một mảnh ghép thiết yếu trong microservices.
Hãy xem các đoạn sau: 04:48 - 09:54 và 26:03 - 26:38. Chú ý đến các vấn đề của "peer-to-peer event chains" và cách workflow automation (orchestration) giải quyết chúng bằng cách cung cấp visibility. Đoạn cuối cùng sẽ kết nối trực tiếp khái niệm này với External Task Pattern.
Nếu bạn quan tâm sâu hơn về lịch sử và lý do tại sao External Task Pattern trở thành khuyến nghị mặc định của Camunda, bài viết trên blog của Bernd Rücker (resource LINK) là một nguồn tham khảo tuyệt vời.
Tổng kết
Qua bài học này, bạn đã được trang bị một khái niệm kiến trúc nền tảng và cực kỳ quan trọng khi làm việc với Camunda trong các hệ thống hiện đại.
Những điểm chính cần nhớ:
- External Task Pattern là một mô hình "Pull": Các worker độc lập chủ động kéo công việc từ engine, trái ngược với mô hình "Push" của
JavaDelegate. - Cơ chế hoạt động: Dựa trên
Topicđể phân loại công việc và chu trìnhFetch & Lockđể giao việc một cách an toàn. - Lợi ích cho Microservices: Tách rời hoàn toàn (decoupling), cho phép kiến trúc đa ngôn ngữ, khả năng mở rộng độc lập, và tăng cường khả năng phục hồi của hệ thống.
- Enabler cho Orchestration: Cung cấp khả năng quan sát và kiểm soát các quy trình nghiệp vụ phức tạp trải dài trên nhiều microservices, giải quyết các điểm yếu của kiến trúc choreography thuần túy.
Xem trước bài học tiếp theo
Chúng ta đã hiểu rõ "cái gì" và "tại sao". Trong bài học tiếp theo, "Cấu hình một Service Task để sử dụng 'External' implementation", chúng ta sẽ bắt đầu đi vào phần "như thế nào". Bạn sẽ được hướng dẫn các bước thực hành đầu tiên để biến một Service Task trong mô hình BPMN của mình thành một External Task, sẵn sàng để một worker bên ngoài có thể nhận việc.