Chào bạn,
Ở bài học trước, chúng ta đã cấu hình một Service Task để "giao việc" ra bên ngoài bằng cách đặt Implementation là External và định nghĩa một Topic. Khi quy trình chạy đến đó, nó sẽ dừng lại và chờ đợi một hệ thống bên ngoài đến nhận và hoàn thành công việc.
Trong bài học hôm nay, chúng ta sẽ xây dựng "hệ thống bên ngoài" đó. Mục tiêu của bài học là implement một External Task Worker bằng Java client để xử lý tác vụ. Bạn sẽ viết một ứng dụng Java độc lập, kết nối tới Camunda engine, "đăng ký" nhận việc từ một topic cụ thể, thực thi logic nghiệp vụ và báo cáo lại cho engine khi hoàn thành. Đây là mảnh ghép cuối cùng để hoàn thiện chu trình của External Task Pattern.
Với kinh nghiệm lập trình và thiết kế kiến trúc của bạn, bạn sẽ thấy mẫu pattern này rất quen thuộc và mạnh mẽ, đặc biệt trong bối cảnh microservices.
1. Kiến trúc của một External Task Worker
Trước khi đi vào code, hãy cùng xem lại cơ chế hoạt động của một worker. Một worker về cơ bản là một client thực hiện một vòng lặp các công việc sau:
- Subscribe (Đăng ký): Worker thông báo cho Process Engine rằng: "Tôi có khả năng xử lý các công việc thuộc topic
X". - Fetch & Lock (Lấy và Khóa): Worker định kỳ hỏi engine: "Có công việc nào mới thuộc topic
Xkhông?". Nếu có, engine sẽ trả về một danh sách các công việc và khóa chúng 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 lấy phải công việc đã được giao. - Execute (Thực thi): Worker thực thi logic nghiệp vụ của nó, ví dụ như gọi một API, xử lý dữ liệu, tạo file, v.v.
- Complete (Hoàn thành): Sau khi thực thi xong, worker gửi một tín hiệu
completetới engine, báo rằng công việc đã hoàn tất. Lúc này, engine sẽ mở khóa task và cho phép process instance đi tiếp.
Sơ đồ sau minh họa trực quan chu trình này:

Trong thế giới Java, Camunda cung cấp các thư viện client để bạn không phải tự mình implement vòng lặp này. Chúng ta sẽ tập trung vào cách tiếp cận hiện đại và phổ biến nhất: sử dụng Spring Boot Starter.
2. Xây dựng Worker với Spring Boot Starter
Vì bạn đã quen thuộc với Spring Boot qua việc thiết lập project Camunda engine, việc tạo một worker bằng Spring Boot Starter sẽ rất tự nhiên. Cách tiếp cận này giúp đơn giản hóa việc cấu hình và tích hợp liền mạch vào hệ sinh thái Spring.
Bước 1: Tạo Project và Thêm Dependency
Điều quan trọng cần nhớ là worker là một ứng dụng độc lập. Vì vậy, bạn sẽ tạo một project Spring Boot hoàn toàn mới cho worker này.
Sau khi có project, bạn cần thêm dependency của Camunda External Task Client cho Spring Boot.
Spring Boot Starter for the External Task Client
Hãy đọc tài liệu sau để biết dependency Maven cần thiết. Đây là thư viện sẽ cung cấp tất cả các công cụ để bạn tạo worker.
Tìm đến "Step 2: Add Dependency" và sao chép đoạn mã dependency cho camunda-bpm-spring-boot-starter-external-task-client vào file pom.xml của project worker mới của bạn. (Lưu ý: phiên bản có thể đã mới hơn, bạn có thể dùng phiên bản tương ứng với Camunda engine của mình).
Dependency này sẽ tự động cấu hình một client có khả năng giao tiếp với Camunda engine.
Bước 2: Cấu hình kết nối
Worker cần biết địa chỉ của Camunda engine để có thể kết nối. Việc này được thực hiện trong file application.properties (hoặc application.yml).
Spring Boot Starter for the External Task Client
Tài liệu tiếp theo sẽ chỉ cho bạn cách cấu hình các thuộc tính cơ bản cho worker.
Đọc phần "Step 4: Add application.yml file". Tập trung vào thuộc tính quan trọng nhất: camunda.bpm.client.base-url. Đây là URL trỏ tới REST API của Camunda engine, thường là http://localhost:8080/engine-rest nếu bạn chạy local.
File application.properties của bạn sẽ có một dòng đơn giản như sau:
# URL của Camunda Engine REST API
camunda.bpm.client.base-url=http://localhost:8080/engine-rest
Chỉ với một dòng này, worker của bạn đã biết "nói chuyện" với ai.
Bước 3: Implement Task Handler
Đây là phần cốt lõi, nơi bạn viết logic nghiệp vụ. Với Spring Boot Starter, bạn chỉ cần tạo một class và sử dụng annotation.
Spring Boot Starter for the External Task Client
Bây giờ, hãy xem cách viết mã để xử lý công việc. Đây là phần bạn sẽ dành nhiều thời gian nhất khi làm việc với external tasks.
Đọc kỹ phần "Step 5: Implement Service Tasks". Chú ý cách họ tạo một class Java implement ExternalTaskHandler và sử dụng hai annotation quan trọng: @Component và @ExternalTaskSubscription("topic-name").
Hãy cùng phân tích một ví dụ handler đơn giản:
import org.camunda.bpm.client.task.ExternalTask;
import org.camunda.bpm.client.task.ExternalTaskHandler;
import org.camunda.bpm.client.task.ExternalTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Configuration;
import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription;
@Component
@ExternalTaskSubscription("charge-card") // <-- Topic name phải khớp với BPMN
public class ChargeCardHandler implements ExternalTaskHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(ChargeCardHandler.class);
@Override
public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) {
// Lấy các biến từ quy trình
String item = externalTask.getVariable("item");
Integer amount = externalTask.getVariable("amount");
LOGGER.info("Đang trừ tiền thẻ tín dụng cho món hàng '{}' với số tiền '{}'...", item, amount);
// --- Logic nghiệp vụ của bạn ở đây ---
// Ví dụ: gọi tới một payment gateway API
// Hoàn thành task
externalTaskService.complete(externalTask);
LOGGER.info("Tác vụ đã được hoàn thành!");
}
}
Những điểm chính trong đoạn code trên:
@Component: Đánh dấu class này là một Spring Bean để Spring quản lý.@ExternalTaskSubscription("charge-card"): Đây là "phép thuật". Annotation này tự động đăng ký handler này với topiccharge-card. Bạn chỉ cần đảm bảo tên topic này khớp chính xác với tên topic bạn đã định nghĩa trong Camunda Modeler ở bài học trước.implements ExternalTaskHandler: Interface yêu cầu bạn phải có một phương thứcexecute.- Phương thức
execute()có 2 tham số quan trọng:ExternalTask externalTask: Đối tượng chứa mọi thông tin về task được giao, như ID, và quan trọng là các biến quy trình (.getVariable()).ExternalTaskService externalTaskService: Dùng để giao tiếp ngược lại với engine. Phương thức phổ biến nhất là.complete()để báo cáo task đã xong.
Dưới đây là một ví dụ trực quan khác về một handler.

3. Chạy Toàn bộ Hệ thống
Bây giờ bạn đã có cả hai phần: Process Engine và Worker. Hãy xem cách chúng phối hợp với nhau.
Luồng thực thi:
- Chạy Camunda Engine: Đảm bảo ứng dụng Spring Boot chứa Camunda engine của bạn đang chạy.
- Deploy Quy trình: Deploy file BPMN đã được cấu hình External Task với topic
charge-cardlên engine. - Bắt đầu một Process Instance: Sử dụng Cockpit hoặc REST API để khởi tạo một instance của quy trình đó với các biến cần thiết (ví dụ:
itemvàamount). Bạn sẽ thấy instance này đang "dừng chờ" tại Service TaskCharge Card. - Chạy Worker: Bây giờ, hãy chạy ứng dụng Spring Boot của worker mà bạn vừa tạo.
- Quan sát:
- Trong console của worker, bạn sẽ thấy log cho biết nó đã fetch và thực thi công việc.
- Quay lại Camunda Cockpit và refresh, bạn sẽ thấy process instance đã đi qua bước
Charge Cardvà tiếp tục (hoặc hoàn thành).
Video sau, mặc dù dành cho Camunda 8, nhưng minh họa rất trực quan khoảnh khắc kỳ diệu khi worker nhận việc và quy trình tự động tiếp tục.
Tutorial: How to Build a Custom Camunda Connector, Using Java
Video này minh họa rất rõ luồng hoạt động mà chúng ta đang xây dựng. Hãy xem cách tác giả chạy ứng dụng worker và quan sát quy trình hoàn thành trên giao diện giám sát.
Xem từ 13:35 đến 15:41. Chú ý các bước: tác giả chạy ứng dụng Java, sau đó kiểm tra lại giao diện giám sát (Operate, tương đương với Cockpit của chúng ta) và thấy rằng quy trình đã tự động đi qua bước service task và hoàn thành.
Tổng kết
Trong bài học này, bạn đã hoàn thành một vòng lặp hoàn chỉnh của External Task Pattern bằng cách xây dựng một worker. Đây là một kỹ năng nền tảng để xây dựng các hệ thống có khả năng mở rộng và linh hoạt với Camunda.
Những điểm chính cần ghi nhớ:
- Worker là một ứng dụng độc lập, thường là một microservice, kết nối tới engine qua REST API.
- Sử dụng Spring Boot Starter cho External Task Client giúp đơn giản hóa việc tạo worker trong môi trường Spring.
- Dependency
camunda-bpm-spring-boot-starter-external-task-clientlà chìa khóa để bắt đầu. - Annotation
@ExternalTaskSubscription("topic-name")là cách khai báo một handler cho một topic cụ thể. - Luồng hoạt động đầy đủ là: Deploy process -> Start instance -> Run worker -> Observe completion.
Xem trước bài học tiếp theo
Chúng ta đã implement thành công một worker. Tuy nhiên, trong thực tế, các tác vụ có thể mất nhiều thời gian để xử lý, và worker có thể gặp sự cố. Điều gì sẽ xảy ra nếu worker bị "chết" giữa chừng sau khi đã nhận việc? Công việc đó sẽ bị "treo" mãi mãi? Bài học tiếp theo, "Cấu hình lock duration và timeout cho các tác vụ chạy dài", sẽ giải quyết chính xác vấn đề này, giúp bạn xây dựng các worker mạnh mẽ và có khả năng chịu lỗi.