Chào bạn,
Trong bài học trước, chúng ta đã cùng nhau tìm hiểu về Exclusive Gateway (XOR), công cụ giúp quy trình của bạn "đưa ra quyết định" bằng cách chọn một trong số nhiều đường đi khả dĩ, giống như câu lệnh if-else trong lập trình.
Hôm nay, chúng ta sẽ tiếp tục khám phá việc điều khiển luồng trong Module 5, nhưng với một kịch bản khác: không phải là "hoặc cái này, hoặc cái kia", mà là "cả cái này VÀ cái kia". Nhiều quy trình nghiệp vụ đòi hỏi phải thực hiện nhiều công việc cùng một lúc. Ví dụ: sau khi một đơn hàng được xác nhận, hệ thống cần phải đồng thời gửi email cho khách hàng và tạo lệnh xuất kho.
Mục tiêu của bài học này là giúp bạn implement Parallel Gateway (AND) để thực thi song song nhiều nhánh công việc. Bạn sẽ học cách chia một luồng quy trình ra nhiều nhánh chạy đồng thời và sau đó hợp nhất chúng lại khi tất cả đã hoàn thành.
1. Parallel Gateway (AND) là gì?
Parallel Gateway, hay còn gọi là AND Gateway, được sử dụng để mô hình hóa các hoạt động diễn ra song song. Nó có hai vai trò chính:
- Splitting (Phân nhánh): Khi một luồng đi vào, gateway sẽ kích hoạt TẤT CẢ các luồng đi ra của nó cùng một lúc. Nó không cần bất kỳ điều kiện nào như Exclusive Gateway.
- Joining (Hợp nhất/Đồng bộ hóa): Khi được sử dụng để hợp nhất các luồng, gateway sẽ ở trạng thái chờ cho đến khi TẤT CẢ các luồng song song đi vào nó hoàn thành. Chỉ sau khi nhánh cuối cùng kết thúc, quy trình mới đi tiếp qua gateway này.
Ký hiệu của Parallel Gateway là một hình thoi với dấu cộng (+) bên trong.

Để có cái nhìn trực quan về cách hoạt động, hãy xem video giải thích sau đây.
BPMN Gateways Explained - ALL YOU NEED TO KNOW
Video này từ kênh Stefano - The Agile Business Analyst sẽ giải thích khái niệm về Parallel Gateway và đưa ra một ví dụ thực tế dễ hiểu.
Hãy xem từ 04:19 đến 05:33. Chú ý cách người trình bày giải thích rằng các hoạt động có thể chạy song song (ví dụ, do các nhân viên hoặc hệ thống khác nhau thực hiện) và nhấn mạnh rằng quy trình chỉ tiếp tục khi tất cả các nhánh đã hoàn thành.
2. Thực hành: Mở rộng Quy trình Xử lý Đơn hàng
Bây giờ, chúng ta sẽ áp dụng Parallel Gateway vào quy trình xử lý đơn hàng của mình. Yêu cầu mới là:
- Sau khi đơn hàng được chấp thuận (dù là tự động hay qua phê duyệt của quản lý), hệ thống cần thực hiện đồng thời hai việc:
- Gửi email xác nhận cho khách hàng.
- Thông báo cho bộ phận kho để chuẩn bị hàng hóa.
Hãy bắt tay vào việc cập nhật mô hình và code.
Bước 1: Cập nhật mô hình BPMN
- Mở file BPMN của bạn trong Camunda Modeler.
- Tìm điểm hợp nhất (merge gateway) sau nhánh phê duyệt của bài học trước. Xóa điểm hợp nhất đó đi.
- Thay vào đó, hãy thêm một Parallel Gateway (Split). Kéo luồng từ cả nhánh "Tự động chấp thuận" và "Quản lý phê duyệt" vào gateway này.
- Từ Parallel Gateway (Split), kéo ra hai luồng song song:
- Nhánh 1: Nối đến một Service Task mới. Đặt tên là "Gửi email xác nhận".
- Nhánh 2: Nối đến một Service Task khác. Đặt tên là "Thông báo cho kho".
- Thêm một Parallel Gateway (Join) thứ hai. Kéo hai luồng từ hai Service Task trên vào gateway này.
- Từ Parallel Gateway (Join), kéo luồng đến End Event.
Sơ đồ của bạn lúc này sẽ có cấu trúc tương tự như sau:

Bước 2: Implement các Service Task
Trong project Spring Boot của bạn, hãy tạo hai lớp JavaDelegate mới để giả lập hai tác vụ trên.
Lớp gửi email:
// Trong file SendConfirmationEmailDelegate.java
package com.example.camunda.delegate;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("sendEmailDelegate")
public class SendConfirmationEmailDelegate implements JavaDelegate {
private final Logger log = LoggerFactory.getLogger(SendConfirmationEmailDelegate.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
log.info("Bắt đầu gửi email xác nhận...");
// Giả lập thời gian xử lý
Thread.sleep(2000); // 2 giây
log.info("Gửi email xác nhận thành công cho đơn hàng: {}", execution.getProcessInstanceId());
}
}
Lớp thông báo cho kho:
// Trong file NotifyWarehouseDelegate.java
package com.example.camunda.delegate;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("notifyWarehouseDelegate")
public class NotifyWarehouseDelegate implements JavaDelegate {
private final Logger log = LoggerFactory.getLogger(NotifyWarehouseDelegate.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
log.info("Bắt đầu thông báo cho kho...");
// Giả lập thời gian xử lý
Thread.sleep(3000); // 3 giây
log.info("Thông báo cho kho thành công cho đơn hàng: {}", execution.getProcessInstanceId());
}
}
Bước 3: Liên kết mã nguồn và mô hình
- Quay lại Camunda Modeler.
- Chọn Service Task "Gửi email xác nhận".
- Trong Properties Panel, tab General, đặt Implementation là Delegate Expression.
- Trong ô Delegate Expression, nhập tên bean Spring bạn đã định nghĩa:
${sendEmailDelegate}. - Làm tương tự cho Service Task "Thông báo cho kho" với giá trị là
${notifyWarehouseDelegate}.
3. "True Parallelism" và Asynchronous Continuations
Đến đây, một câu hỏi quan trọng xuất hiện, đặc biệt đối với một developer như bạn: Liệu hai Service Task này có thực sự chạy trên hai thread khác nhau không?
Câu trả lời mặc định là KHÔNG.
Camunda Engine, để đảm bảo tính nhất quán của giao dịch (transaction), sẽ cố gắng thực thi các tác vụ trong cùng một thread. Sau khi Parallel Gateway (Split) được kích hoạt, nó sẽ thực thi "Gửi email xác nhận", đợi nó xong, rồi mới thực thi "Thông báo cho kho". Về mặt logic BPMN là song song, nhưng về mặt kỹ thuật lại là tuần tự.
Để đạt được sự song song thực sự (true parallelism), chúng ta cần báo cho engine tạo ra các điểm ngắt giao dịch. Tại các điểm này, engine sẽ lưu trạng thái quy trình vào database và tạo ra các "job" để Job Executor (một thành phần chạy nền) thực thi. Job Executor có một pool các thread, cho phép nó xử lý nhiều job cùng lúc.
Cách làm là sử dụng Asynchronous Continuations.
Để hiểu rõ hơn về vấn đề này và giải pháp, hãy xem một cuộc thảo luận rất thực tế trên diễn đàn của Camunda.
Can't run two service task parallelly?
Chủ đề này trên diễn đàn Camunda mô tả chính xác vấn đề bạn có thể gặp phải: hai service task trong một parallel gateway nhưng lại chạy tuần tự. Các câu trả lời giải thích lý do tại sao và cách khắc phục.
Hãy đọc kỹ cuộc thảo luận này: Bài đăng đầu tiên: Người dùng mô tả vấn đề. Câu trả lời của StephanHaarmann: Chú ý đến phần giải thích về "internal transaction management" và cách "parallelism will be resolved to a sequential order". Quan trọng nhất là giải pháp được đề xuất: đặt cờ asynchronous before. Câu trả lời của Ingo: Phần này cung cấp một lời giải thích sâu sắc hơn, nêu rõ lý do tại sao engine lại cẩn thận như vậy (để tránh ghi đè biến, deadlock) và phân biệt giữa JavaDelegate với User Tasks hoặc External Tasks (vốn đã có tính song song tự nhiên do là các "wait state").
Bước 4: Cấu hình Asynchronous Before
Dựa trên những gì bạn vừa đọc, hãy cập nhật lại mô hình BPMN:
- Chọn Service Task "Gửi email xác nhận".
- Trong Properties Panel, tìm mục Asynchronous Continuations.
- Tích vào ô Asynchronous Before.
- Lặp lại các bước 1-3 cho Service Task "Thông báo cho kho".
Bằng cách này, khi quy trình đến Parallel Gateway, nó sẽ tạo ra hai job riêng biệt. Job Executor sẽ lấy cả hai job này và thực thi chúng trên các thread khác nhau từ thread pool của nó.
Bước 5: Deploy và kiểm tra
- Lưu lại file BPMN và chạy lại ứng dụng Spring Boot của bạn.
- Khởi tạo một process instance mới và hoàn thành các User Task ban đầu.
- Theo dõi console log của ứng dụng. Bạn sẽ thấy các thông điệp log từ hai delegate của chúng ta. Thay vì một cái hoàn thành rồi cái kia mới bắt đầu, bạn sẽ thấy chúng xen kẽ nhau, ví dụ:
Thứ tự có thể thay đổi, nhưng điều quan trọng là chúng bắt đầu gần như cùng lúc trên các thread khác nhau (INFO --- [aTaskExecutor-1] ... : Bắt đầu gửi email xác nhận... INFO --- [aTaskExecutor-2] ... : Bắt đầu thông báo cho kho... INFO --- [aTaskExecutor-1] ... : Gửi email xác nhận thành công... INFO --- [aTaskExecutor-2] ... : Thông báo cho kho thành công...camundaTaskExecutor-1,camundaTaskExecutor-2). - Vào Camunda Cockpit, bạn cũng có thể thấy trong một khoảnh khắc ngắn, có hai token đang hoạt động trên cả hai nhánh song song trước khi chúng được hợp nhất.
Tổng kết
Trong bài học này, bạn đã học được một khái niệm điều khiển luồng mạnh mẽ và rất phổ biến trong BPM.
Những điểm chính cần nhớ:
- Parallel Gateway (AND) được dùng để thực thi nhiều nhánh công việc cùng một lúc.
- Gateway này có hai chức năng: phân nhánh (split) để kích hoạt tất cả các luồng ra và hợp nhất (join) để chờ tất cả các luồng vào hoàn thành.
- Theo mặc định, các Service Task
JavaDelegatetrong các nhánh song song của Camunda 7 sẽ chạy tuần tự trong cùng một thread. - Để đạt được sự song song thực sự về mặt kỹ thuật, bạn phải sử dụng Asynchronous Continuations (đặt cờ
Asynchronous Before) để tạo ra các job cho Job Executor xử lý.
Xem trước bài học tiếp theo
Chúng ta đã biết cách chọn một trong nhiều (XOR) và cách thực hiện tất cả (AND). Nhưng nếu chúng ta muốn một kịch bản linh hoạt hơn thì sao? Ví dụ, dựa trên loại sản phẩm, chúng ta có thể cần hoặc thông báo cho bộ phận A, hoặc bộ phận B, hoặc cả hai?
Trong bài học tiếp theo, chúng ta sẽ tìm hiểu về Inclusive Gateway (OR). Đây là loại gateway kết hợp sức mạnh của cả Exclusive và Parallel, cho phép bạn kích hoạt một hoặc nhiều nhánh dựa trên các điều kiện.