Chào bạn,
Trong bài học trước, chúng ta đã học cách sử dụng Expression Language (EL) để đọc và điều khiển luồng quy trình dựa trên dữ liệu. Bạn đã thấy EL mạnh mẽ như thế nào khi định nghĩa các điều kiện rẽ nhánh cho gateway chỉ bằng một dòng biểu thức như ${approved == true}.
Tuy nhiên, khi các quy trình trở nên phức tạp hơn, việc tất cả các tác vụ đều đọc và ghi vào cùng một "không gian chung" của các biến quy trình (process variables) có thể dẫn đến một số vấn đề:
- Xung đột tên biến: Một tác vụ có thể vô tình ghi đè lên một biến mà tác vụ khác cần.
- Thiếu tính đóng gói (Encapsulation): Một Service Task có thể truy cập vào mọi biến của quy trình, ngay cả những biến không liên quan đến nó. Điều này tương tự như việc tất cả các biến trong một chương trình đều là biến toàn cục (global variables), một thực hành mà các lập trình viên thường tránh.
- Khó tái sử dụng: Một Service Task được thiết kế để hoạt động với các biến có tên cụ thể (
orderAmount,customerTier) sẽ khó tái sử dụng trong một quy trình khác có các biến được đặt tên khác.
Để giải quyết những vấn đề này, Camunda cung cấp một cơ chế mạnh mẽ. Mục tiêu của bài học hôm nay là giúp bạn ánh xạ (map) biến đầu vào/đầu ra cho một Service Task. Kỹ thuật này cho phép bạn kiểm soát chính xác dữ liệu nào được truyền vào một tác vụ và dữ liệu nào được trả ra, giống như cách bạn truyền tham số cho một hàm và nhận lại giá trị trả về trong lập trình.
1. Nguyên tắc cơ bản của Input/Output Mapping
Về cơ bản, Input/Output Mapping hoạt động như một lớp "adapter" giữa phạm vi biến toàn cục của quy trình (process scope) và phạm vi biến cục bộ của một tác vụ (task-local scope).
- Input Mapping (Ánh xạ đầu vào): Trước khi một tác vụ (ví dụ: Service Task) được thực thi, cơ chế ánh xạ sẽ lấy giá trị từ các biến quy trình, xử lý chúng nếu cần, và tạo ra các biến cục bộ chỉ tồn tại trong phạm vi của tác vụ đó. Logic bên trong tác vụ (ví dụ: code Java của bạn) sẽ chỉ làm việc với các biến cục bộ này.
- Output Mapping (Ánh xạ đầu ra): Sau khi tác vụ hoàn thành, cơ chế ánh xạ sẽ lấy các giá trị từ các biến cục bộ (có thể là kết quả tính toán), xử lý chúng nếu cần, và cập nhật hoặc tạo mới các biến trong phạm vi toàn cục của quy trình.
Cách làm này mang lại lợi ích rõ ràng, đặc biệt với kinh nghiệm lập trình của bạn:
- Tính đóng gói: Logic nghiệp vụ của bạn bên trong một
JavaDelegategiờ đây chỉ phụ thuộc vào các "tham số" được truyền vào, chứ không phải toàn bộ trạng thái của quy trình. - Tính linh hoạt và tái sử dụng: Bạn có thể dễ dàng tái sử dụng một Service Task trong nhiều quy trình khác nhau bằng cách ánh xạ các biến quy trình tương ứng vào các tham số đầu vào mà tác vụ đó mong đợi.
- Chuyển đổi dữ liệu: Bạn có thể thực hiện các phép biến đổi dữ liệu đơn giản (ghép chuỗi, tính toán cơ bản) ngay tại lớp ánh xạ mà không cần thay đổi code Java.
2. Cấu hình Input/Output Mapping trong Camunda Modeler
Để hiểu rõ hơn, hãy xem cách thực hiện việc này trong Camunda Modeler.
Service Task - Coding | Camunda Exercises
Tài liệu thực hành này từ Camunda University sẽ hướng dẫn bạn qua một ví dụ đơn giản về xác minh tuổi. Phần quan trọng nhất là cách họ giới thiệu việc sử dụng tab 'Input/Output' như một "best practice" để tăng tính tái sử dụng và mạnh mẽ cho các quy trình.
Hãy đọc kỹ phần đầu tiên có tiêu đề 'Variables'. Tập trung vào đoạn văn bắt đầu bằng "Both variables, birthdate and age, are defined as global process variables..." cho đến hết phần đó. Chú ý cách họ đề xuất chuyển từ biến toàn cục sang biến cục bộ thông qua tab 'Input/Output' của Service Task 'Calculate Age'.
Như bạn đã đọc, việc cấu hình được thực hiện trong tab Input/Output của Properties Panel.

Trong tab này, bạn có hai phần chính: Input Parameters và Output Parameters.
Input Parameters
Đối với mỗi tham số đầu vào, bạn cần định nghĩa:
- Name: Tên của biến cục bộ sẽ được tạo ra trong phạm vi của tác vụ.
- Type: Cách giá trị được gán. Các loại phổ biến là:
- Text: Gán một chuỗi ký tự tĩnh.
- Expression: Gán giá trị của một biểu thức EL. Đây là cách phổ biến nhất, ví dụ:
${orderId}để lấy giá trị của biến quy trìnhorderId. - Script: Viết một đoạn script ngắn (ví dụ: JavaScript, Groovy) để tính toán và trả về giá trị.
- Value: Giá trị hoặc biểu thức tương ứng với Type đã chọn.
Output Parameters
Đối với mỗi tham số đầu ra, bạn cần định nghĩa:
- Name: Tên của biến quy trình sẽ được tạo hoặc cập nhật.
- Type: Tương tự như Input Parameters.
- Source: Giá trị sẽ được gán cho biến quy trình. Giá trị này thường là tên của một biến cục bộ đã được tạo ra bởi logic của tác vụ, ví dụ:
${localResult}.
3. Ví dụ thực tế: Ánh xạ cho Service Task gọi API
Một trong những ứng dụng phổ biến nhất của Service Task là gọi một REST API bên ngoài. Input/Output mapping trở nên cực kỳ hữu ích trong kịch bản này để chuẩn bị request và xử lý response.
Camunda cung cấp một http-connector tích hợp sẵn, cho phép bạn thực hiện các lệnh gọi HTTP mà không cần viết code Java. Hãy xem cách Input/Output mapping được sử dụng trong trường hợp này.
Process automation without Java classes - GitHub
Đây là một ví dụ nâng cao hơn từ đội ngũ tư vấn của Camunda, cho thấy cách tự động hóa một quy trình chỉ bằng script và các lệnh gọi HTTP. Hãy chú ý đến cấu trúc XML của Service Task để thấy rõ cách các tham số đầu vào và đầu ra được định nghĩa.
Hãy đọc kỹ phần 'Using connectors'. Tập trung vào đoạn mã XML của <bpmn:serviceTask>. Phân tích các thẻ <camunda:inputParameter> và <camunda:outputParameter> bên trong nó. Cụ thể: Input Parameters: Chú ý cách họ định nghĩa url, method, headers, và đặc biệt là payload. Tham số payload sử dụng một script để chuyển đổi biến quy trình claims thành chuỗi JSON: <camunda:script scriptFormat="javascript"><![CDATA[claims.toString();]]></camunda:script>. Output Parameter: Chú ý cách họ lấy response từ lệnh gọi HTTP và dùng script để chuyển nó thành một đối tượng JSON trước khi lưu vào biến quy trình resultCheckMandatoryFields: <camunda:script scriptFormat="javaScript">S(response);</camunda:script>.
Ví dụ trên cho thấy sức mạnh của việc ánh xạ:
- Đầu vào: Chúng ta không truyền thẳng đối tượng
claimsvào connector. Thay vào đó, chúng ta biến đổi nó thành chuỗi JSON, là định dạng mà API endpoint mong đợi. - Đầu ra: Lệnh gọi HTTP trả về một chuỗi. Chúng ta không lưu chuỗi thô này vào biến quy trình. Thay vào đó, chúng ta biến đổi nó trở lại thành một đối tượng JSON có cấu trúc bằng hàm
S(response)của SPIN, giúp các bước tiếp theo trong quy trình dễ dàng xử lý hơn.
4. Thực hành: Áp dụng Input/Output Mapping vào quy trình của bạn
Bây giờ, hãy quay lại quy trình xử lý đơn hàng của chúng ta và áp dụng kỹ thuật này.
Ở bài học trước, ProcessResultDelegate của bạn đang truy cập trực tiếp các biến quy trình như orderId và approved bằng execution.getVariable("..."). Chúng ta sẽ thay đổi điều này để nó nhận các tham số đầu vào một cách tường minh.
Yêu cầu:
- Cấu hình Service Task "Xử lý Kết quả" để truyền vào hai biến cục bộ:
localOrderIdvàstatusMessage. localOrderIdsẽ được ánh xạ từ biến quy trìnhorderId.statusMessagesẽ là một chuỗi được tạo động dựa trên giá trị của biếnapproved.- Cập nhật code của
ProcessResultDelegateđể đọc các biến cục bộ mới này thay vì các biến quy trình. - Cấu hình một tham số đầu ra để lưu thời gian xử lý vào một biến quy trình mới là
processingTimestamp.
Các bước thực hiện:
-
Mở Camunda Modeler: Mở file BPMN của bạn.
-
Cấu hình Input Parameters:
- Chọn Service Task "Xử lý Kết quả".
- Trong Properties Panel, đi đến tab Input/Output.
- Trong phần Input Parameters, nhấn nút
+để thêm một tham số mới:- Name:
localOrderId - Type:
Expression - Value:
${orderId}
- Name:
- Thêm một tham số đầu vào khác:
- Name:
statusMessage - Type:
Script - Script Format:
javascript - Script Type:
Inline Script - Script:
approved ? "Đơn hàng đã được duyệt thành công." : "Trạng thái đơn hàng không hợp lệ.";
(Biểu thức tam phân này sẽ tạo ra một thông điệp dựa trên biến quy trìnhapproved).
- Name:
-
Cập nhật Java Delegate:
- Mở lớp
ProcessResultDelegate.javatrong IDE của bạn. - Thay đổi nội dung của phương thức
executenhư sau:
@Override public void execute(DelegateExecution execution) throws Exception { // Lấy các biến cục bộ đã được ánh xạ String orderId = (String) execution.getVariable("localOrderId"); String message = (String) execution.getVariable("statusMessage"); System.out.println("--- Xử lý kết quả cho đơn hàng ---"); System.out.println("ID Đơn hàng: " + orderId); System.out.println("Thông điệp: " + message); // Tạo một biến cục bộ để ánh xạ ra ngoài String timestamp = java.time.Instant.now().toString(); execution.setVariable("timestamp", timestamp); System.out.println("Thời gian xử lý: " + timestamp); System.out.println("---------------------------------"); }Lưu ý rằng bây giờ chúng ta đang gọi
execution.getVariable("localOrderId"), không phải"orderId". Đồng thời, chúng ta tạo một biến cục bộ mới tên làtimestamp. - Mở lớp
-
Cấu hình Output Parameters:
- Quay lại Camunda Modeler, vẫn ở tab Input/Output của Service Task.
- Trong phần Output Parameters, thêm một tham số mới:
- Name:
processingTimestamp(đây là tên biến quy trình mới sẽ được tạo) - Type:
Expression - Source:
${timestamp}(đây là tên biến cục bộ chúng ta đã tạo trong code Java)
- Name:
-
Deploy và kiểm tra:
- Deploy lại ứng dụng Spring Boot của bạn.
- Bắt đầu một process instance mới.
- Tại User Task, claim task, tạo biến
approvedlàtruevà complete. - Kiểm tra log console. Bạn sẽ thấy các thông điệp được in ra từ
ProcessResultDelegate, sử dụng đúng các giá trị đã được ánh xạ. - Vào Camunda Cockpit, xem lại process instance vừa hoàn thành. Trong tab Variables, bạn sẽ thấy một biến mới tên là
processingTimestampđã được tạo ra với giá trị là thời gian xử lý.
Bài thực hành này minh họa toàn bộ vòng đời của việc ánh xạ: truyền dữ liệu vào, xử lý, và truyền kết quả ra.
5. Tóm tắt và xem trước
Trong bài học này, bạn đã học được một trong những kỹ thuật quan trọng nhất để quản lý dữ liệu trong Camunda một cách chuyên nghiệp.
Những điểm chính cần nhớ:
- Input/Output Mapping hoạt động như một "hợp đồng dữ liệu" cho các tác vụ, giúp tăng tính đóng gói, linh hoạt và tái sử dụng.
- Input Mapping tạo ra các biến cục bộ cho tác vụ từ các biến quy trình.
- Output Mapping cập nhật các biến quy trình từ kết quả xử lý bên trong tác vụ.
- Bạn có thể sử dụng Expression hoặc Script để thực hiện các phép biến đổi dữ liệu đơn giản ngay tại lớp ánh xạ.
- Kỹ thuật này cực kỳ hữu ích khi tích hợp với các hệ thống bên ngoài, chẳng hạn như gọi REST API thông qua HTTP Connector.
Xem trước bài học tiếp theo
Chúng ta đã làm việc với các kiểu dữ liệu cơ bản như String, Boolean và Number. Nhưng trong các ứng dụng thực tế, dữ liệu thường phức tạp hơn nhiều, ví dụ như các đối tượng Customer, Order, Product với nhiều thuộc tính.
Trong bài học tiếp theo, chúng ta sẽ khám phá cách lưu trữ và truy xuất các đối tượng Java phức tạp (POJOs) dưới dạng biến quy trình. Bạn sẽ học cách Camunda tuần tự hóa (serialize) các đối tượng này và cách bạn có thể tương tác với chúng trong quy trình, bao gồm cả việc sử dụng Input Mapping để trích xuất các thuộc tính cụ thể từ một đối tượng lớn.