Chào bạn,
Trong các bài học trước, chúng ta đã làm quen với việc quản lý dữ liệu trong quy trình bằng các biến có kiểu dữ liệu cơ bản như String, Boolean, Integer. Chúng ta cũng đã học cách sử dụng Input/Output Mapping để tạo "hợp đồng dữ liệu" cho các tác vụ, giúp code của bạn trở nên đóng gói và dễ tái sử dụng hơn.
Tuy nhiên, trong các ứng dụng thực tế, dữ liệu hiếm khi đơn giản như vậy. Chúng ta thường làm việc với các cấu trúc dữ liệu phức tạp, chẳng hạn như đối tượng Customer với thông tin chi tiết, một Order với danh sách các sản phẩm, hay một Claim với nhiều trường dữ liệu.
Mục tiêu của bài học hôm nay là giúp bạn nắm vững cách lưu trữ và truy xuất các đối tượng Java phức tạp dưới dạng biến quy trình. Đây là một kỹ năng cực kỳ quan trọng, cho phép bạn mô hình hóa dữ liệu trong quy trình một cách tự nhiên và phù hợp với domain nghiệp vụ của bạn, giống như cách bạn định nghĩa các model object trong một ứng dụng.
1. Phép màu đằng sau việc lưu trữ đối tượng: Serialization
Khi bạn gọi runtimeService.setVariable("myVar", myObject), Camunda Engine không thể lưu trực tiếp đối tượng Java của bạn vào cơ sở dữ liệu (thường là CSDL quan hệ). Thay vào đó, nó phải thực hiện một quá trình gọi là serialization: chuyển đổi trạng thái của đối tượng thành một định dạng có thể lưu trữ hoặc truyền đi (ví dụ: một chuỗi byte hoặc một chuỗi JSON). Khi cần truy xuất lại, nó sẽ thực hiện quá trình ngược lại là deserialization.

Với kinh nghiệm lập trình của bạn, khái niệm này hẳn rất quen thuộc. Nó tương tự như cách các thư viện như Jackson hay Gson chuyển đổi một POJO (Plain Old Java Object) thành một chuỗi JSON để gửi qua API và ngược lại.
Để hiểu rõ hơn về bản chất của serialization, đặc biệt là với JSON, hãy xem video ngắn sau.
Serialize and Deserialize JSON using Jackson
Video này từ kênh The Life Code giải thích rất rõ về Jackson, một thư viện phổ biến trong hệ sinh thái Java để thực hiện serialization và deserialization giữa đối tượng Java và JSON. Việc hiểu rõ khái niệm này sẽ giúp bạn nắm bắt cách Camunda xử lý các đối tượng phức tạp.
Hãy xem các phần sau: What is Jackson? (00:16 - 02:20): Để nắm bắt khái niệm cốt lõi về serialization và deserialization. Serialize nested objects (27:49 - 31:13): Xem cách Jackson xử lý một đối tượng (Car) chứa một đối tượng khác (Engine). Deserialize nested objects (31:13 - 34:54): Xem quá trình ngược lại, từ JSON có cấu trúc phức tạp trở lại các đối tượng Java. Điều này tương tự như cách Camunda xử lý các biến quy trình là đối tượng phức tạp.
2. Các định dạng Serialization trong Camunda
Camunda hỗ trợ nhiều định dạng serialization, nhưng có hai loại chính bạn cần quan tâm.
Process Variables | Camunda Docs
Tài liệu chính thức của Camunda giải thích chi tiết về các loại biến và cách chúng được lưu trữ. Đọc phần này sẽ giúp bạn hiểu rõ các lựa chọn serialization mà engine cung cấp.
Hãy đọc các phần sau trong tài liệu: Supported Variable Values: Bắt đầu từ bảng các kiểu dữ liệu, chú ý đến loại object. Phần này giới thiệu về việc các đối tượng tùy chỉnh sẽ được serialize. Object Value Serialization: Đọc kỹ phần này để hiểu về serializationDataFormat. Chú ý sự khác biệt giữa serializer application/x-java-serialized-object mặc định và các định dạng dựa trên văn bản như JSON và XML được cung cấp bởi plugin Camunda Spin. Java serialization format: Đọc phần cảnh báo về các vấn đề bảo mật của Java serialization và khuyến nghị sử dụng các định dạng khác.
Sau khi đọc tài liệu, chúng ta có thể tóm tắt như sau:
| Định dạng Serialization | Cách hoạt động & Yêu cầu | Ưu điểm | Nhược điểm |
|---|---|---|---|
| Java Serialization (Mặc định) | Sử dụng cơ chế serialization sẵn có của Java. Đối tượng của bạn phải implements java.io.Serializable. |
Dễ sử dụng, không cần cấu hình. | - Chỉ tương thích với Java. - Khó đọc. - Dễ gây lỗi khi phiên bản class thay đổi. - Có thể có rủi ro bảo mật. |
| JSON / XML (Qua Camunda SPIN) | Sử dụng thư viện SPIN để chuyển đổi đối tượng thành chuỗi JSON hoặc XML. | - Dễ đọc, dễ debug. - Ngôn ngữ độc lập: Các microservice viết bằng Node.js, Python có thể đọc/ghi biến này. - Linh hoạt hơn. |
- Yêu cầu có dependency camunda-spin-dataformat-json-jackson. (Thường đã có sẵn trong Spring Boot Starter). |
Khuyến nghị: Gần như trong mọi trường hợp, đặc biệt là các kiến trúc hướng microservice, bạn nên ưu tiên sử dụng JSON làm định dạng serialization cho các đối tượng phức tạp.
3. Thực hành: Làm việc với đối tượng phức tạp
Giờ chúng ta sẽ áp dụng kiến thức này vào quy trình xử lý đơn hàng. Thay vì truyền các biến riêng lẻ như orderId, customerName, amount, chúng ta sẽ tạo một đối tượng duy nhất là OrderDetails.
Bước 1: Tạo lớp OrderDetails
Trong project Spring Boot của bạn, tạo một lớp POJO mới tên là OrderDetails.java.
package com.example.camunda.process.solution;
import java.io.Serializable;
import java.util.List;
// implement Serializable là cần thiết để Camunda có thể xử lý đối tượng này
public class OrderDetails implements Serializable {
private static final long serialVersionUID = 1L;
private String orderId;
private String customerName;
private List<String> items;
private Double totalAmount;
// Constructors, Getters and Setters
public OrderDetails() {}
public OrderDetails(String orderId, String customerName, List<String> items, Double totalAmount) {
this.orderId = orderId;
this.customerName = customerName;
this.items = items;
this.totalAmount = totalAmount;
}
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public List<String> getItems() { return items; }
public void setItems(List<String> items) { this.items = items; }
public Double getTotalAmount() { return totalAmount; }
public void setTotalAmount(Double totalAmount) { this.totalAmount = totalAmount; }
@Override
public String toString() {
return "OrderDetails{" +
"orderId='" + orderId + '\'' +
", customerName='" + customerName + '\'' +
", items=" + items +
", totalAmount=" + totalAmount +
'}';
}
}
Lưu ý: Mặc dù chúng ta sẽ ưu tiên JSON, việc implements Serializable vẫn là một good practice vì engine có thể cần đến nó trong một số trường hợp, và nó cũng là một cách đánh dấu rõ ràng rằng đối tượng này được thiết kế để có thể được truyền đi.
Bước 2: Bắt đầu quy trình với biến đối tượng
Có nhiều cách để khởi tạo quy trình với biến đối tượng. Một cách rất phổ biến trong môi trường microservice là qua REST API. Bạn có thể chỉ định rõ rằng biến của bạn là một đối tượng JSON.
Đây là một ví dụ về payload của lời gọi POST /process-definition/key/{key}/start để khởi tạo quy trình của chúng ta. Bạn có thể dùng Postman hoặc một REST client khác để thử.
{
"variables": {
"orderDetails": {
"value": "{\"orderId\":\"A123\",\"customerName\":\"Trong Nguyen\",\"items\":[\"Laptop\",\"Mouse\"],\"totalAmount\":25500000.0}",
"type": "Object",
"valueInfo": {
"objectTypeName": "com.example.camunda.process.solution.OrderDetails",
"serializationDataFormat": "application/json"
}
}
}
}
objectTypeName cho Camunda biết lớp Java tương ứng khi cần deserialize. serializationDataFormat yêu cầu engine sử dụng SPIN để xử lý biến này dưới dạng JSON.
Một ví dụ thực tế khác cho thấy cách lưu một danh sách lỗi dưới dạng JSON từ một script task.
Process automation without Java classes
Ví dụ này từ GitHub của Camunda Consulting cho thấy một cách tiếp cận "script-heavy" để tự động hóa. Nó minh họa rất tốt cách các cấu trúc dữ liệu phức tạp (như một mảng JSON) được lưu trữ và thao tác.
Hãy xem hai phần: Using script tasks: Chú ý dòng S(errorList, "application/json");. Đây là cú pháp của SPIN để tạo một biến kiểu JSON từ một đối tượng JavaScript. Payload của lời gọi REST: Kéo xuống dưới cùng, xem ví dụ về payload JSON. Chú ý cách biến claims được định nghĩa với type: "json" và serializationDataFormat. Đây chính là cách bạn nói với Camunda rằng "hãy lưu trữ chuỗi này dưới dạng một đối tượng JSON có thể truy vấn, chứ không phải một chuỗi ký tự thông thường".
Bước 3: Truy xuất thuộc tính đối tượng trong quy trình
Một trong những ưu điểm lớn nhất của việc lưu đối tượng là bạn có thể truy cập các thuộc tính của nó một cách dễ dàng bằng Expression Language (EL) ở bất kỳ đâu trong quy trình.
Mở file BPMN của bạn trong Camunda Modeler.
- Tại User Task "Duyệt đơn hàng": Bạn có thể thay đổi tên (Name) của task để hiển thị thông tin chi tiết hơn.
- Name:
Duyệt đơn hàng cho KH: ${orderDetails.customerName} - Camunda sẽ tự động gọi
getOrderDetails().getCustomerName()khi hiển thị tên task.
- Name:
- Tại Exclusive Gateway "Đơn hàng > 5M?": Bạn có thể thay đổi điều kiện để đọc trực tiếp từ thuộc tính của đối tượng.
- Chọn mũi tên "Có", trong tab "Condition", đặt Condition Expression là:
${orderDetails.totalAmount > 5000000}.
- Chọn mũi tên "Có", trong tab "Condition", đặt Condition Expression là:
Bước 4: Truy xuất đối tượng trong Java Delegate
Bây giờ, hãy cập nhật ProcessResultDelegate để làm việc với đối tượng OrderDetails thay vì các biến riêng lẻ.
@Override
public void execute(DelegateExecution execution) throws Exception {
// Lấy toàn bộ đối tượng OrderDetails
// Camunda sẽ tự động deserialize chuỗi JSON trở lại thành object
OrderDetails details = (OrderDetails) execution.getVariable("orderDetails");
// Nếu bạn muốn lấy dưới dạng SPIN object để thao tác với JSON
// SpinJsonNode jsonDetails = (SpinJsonNode) execution.getVariableTyped("orderDetails").getValue();
boolean approved = (Boolean) execution.getVariable("approved");
System.out.println("--- Xử lý kết quả cho đơn hàng ---");
if (details != null) {
System.out.println("Thông tin đơn hàng: " + details.toString());
}
System.out.println("Trạng thái duyệt: " + (approved ? "Đã duyệt" : "Bị từ chối"));
System.out.println("---------------------------------");
}
Deploy và kiểm tra:
- Deploy lại ứng dụng Spring Boot.
- Sử dụng Postman để bắt đầu một process instance mới với payload JSON như ở Bước 2.
- Vào Camunda Tasklist, bạn sẽ thấy tên của User Task đã hiển thị tên khách hàng.
- Hoàn thành task (nhớ đặt biến
approved). - Kiểm tra console log, bạn sẽ thấy
ProcessResultDelegatein ra toàn bộ thông tin của đối tượngOrderDetails. - Trong Cockpit, xem process instance vừa chạy. Ở tab Variables, bạn sẽ thấy biến
orderDetails. Nhấn vào biểu tượng{}bên cạnh, bạn có thể xem nội dung JSON đã được serialize, cực kỳ hữu ích cho việc debug.
4. Tổng kết
Qua bài học này, bạn đã trang bị cho mình một công cụ cực kỳ mạnh mẽ để xử lý dữ liệu trong Camunda.
Những điểm chính cần nhớ:
- Camunda lưu trữ các đối tượng phức tạp bằng cách serialization.
- JSON là định dạng serialization được khuyến nghị vì tính linh hoạt, dễ đọc và khả năng tương tác cao (đặc biệt quan trọng cho microservices).
- Sử dụng Camunda SPIN để làm việc với các biến JSON/XML.
- Khi một đối tượng được lưu trong biến quy trình, bạn có thể dễ dàng truy cập các thuộc tính của nó bằng Expression Language (ví dụ:
${myObject.myProperty}). - Khi lấy biến trong code Java, Camunda sẽ tự động deserialize về lại POJO nếu có class tương ứng trong classpath.
Xem trước bài học tiếp theo
Chúng ta đã dành cả Module 4 để tìm hiểu sâu về cách quản lý dữ liệu và biến trong quy trình. Bây giờ bạn đã có nền tảng vững chắc về dữ liệu, đã đến lúc chúng ta khám phá các cấu trúc điều khiển luồng phức tạp hơn.
Trong bài học đầu tiên của Module 5, chúng ta sẽ đi sâu vào Exclusive Gateway (XOR), tìm hiểu cách implement các điều kiện rẽ nhánh phức tạp hơn bằng cách sử dụng các biến quy trình mà bạn vừa học cách quản lý.