Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 35

Báo cáo lỗi BPMN từ External Worker

Recommended resources

Chào bạn,

Trong bài học trước, chúng ta đã tìm hiểu cách xử lý các lỗi kỹ thuật (technical errors) bằng cách sử dụng handleFailure. Bạn đã học cách bắt các exception, báo cáo lỗi về engine, và quản lý chu kỳ thử lại (retry) cho đến khi một sự cố (incident) được tạo ra để con người can thiệp.

Tuy nhiên, không phải mọi "lỗi" đều là sự cố kỹ thuật. Đôi khi, một quy trình không thể tiếp tục theo luồng chính vì một lý do nghiệp vụ đã được dự đoán trước. Ví dụ, một đơn hàng bị từ chối vì khách hàng không đủ tín dụng. Đây không phải là lỗi hệ thống; đó là một kết quả nghiệp vụ hợp lệ và cần được xử lý một cách tường minh.

Bài học hôm nay sẽ tập trung vào mục tiêu này: Báo cáo lỗi nghiệp vụ (BPMN Error) từ External Worker về process engine. Chúng ta sẽ khám phá cách làm cho quy trình của bạn phản ứng với các kịch bản nghiệp vụ khác nhau bằng cách "ném" một lỗi nghiệp vụ từ worker và "bắt" nó trong sơ đồ BPMN để chuyển hướng quy trình sang một luồng xử lý thay thế.


1. Lỗi Kỹ thuật vs. Lỗi Nghiệp vụ: Một Lần Nhìn Lại

Hãy củng cố lại sự khác biệt cốt lõi này:

  • Lỗi Kỹ thuật (Technical Error): Xử lý bằng handleFailure. Gây ra retries và cuối cùng là incidents. Mục tiêu là sửa chữa và thử lại tác vụ hiện tại. Ví dụ: Mất kết nối mạng, API trả về lỗi 503.
  • Lỗi Nghiệp vụ (BPMN Error): Xử lý bằng handleBpmnError. Kích hoạt một luồng xử lý thay thế đã được mô hình hóa trong BPMN. Mục tiêu là chuyển hướng quy trình một cách có kiểm soát. Ví dụ: Khách hàng không đủ điều kiện, sản phẩm hết hàng.

Để xử lý một lỗi nghiệp vụ, chúng ta cần hai thành phần hoạt động cùng nhau:

  1. Bắt lỗi (Catching the Error): Trong sơ đồ BPMN, chúng ta sử dụng một Error Boundary Event để "lắng nghe" một lỗi nghiệp vụ cụ thể tại một tác vụ.
  2. Ném lỗi (Throwing the Error): Từ External Worker, chúng ta gọi một phương thức đặc biệt để báo cáo rằng lỗi nghiệp vụ đó đã xảy ra.

Hãy cùng xem cách thiết lập cơ chế này.



2. Mô hình hóa việc "Bắt" Lỗi với Error Boundary Event

Bước đầu tiên là chuẩn bị quy trình của bạn để có thể xử lý lỗi nghiệp vụ. Điều này được thực hiện trực quan trong Camunda Modeler.

Bạn sẽ gắn một Error Boundary Event vào biên của Service Task mà External Worker của bạn đang thực thi. Sự kiện này hoạt động như một người bảo vệ, chờ đợi một tín hiệu lỗi cụ thể.

BPMN Diagram: Handling Business Errors from an External Service Task
Sơ đồ BPMN minh họa một Error Boundary Event. Nếu 'External Service Task' báo cáo lỗi 'Data Inconsistent', quy trình sẽ chuyển sang tác vụ 'Notify Admin' thay vì đi theo luồng thông thường.

Để cấu hình sự kiện này, thuộc tính quan trọng nhất là Error Code. Đây là một chuỗi định danh duy nhất (ví dụ: CREDIT_INSUFFICIENT, PRODUCT_OUT_OF_STOCK) đóng vai trò là "chìa khóa" để kết nối giữa lỗi được ném từ worker và sự kiện bắt lỗi trong mô hình.

Camunda Platform 7 Tutorial: Setting Up and Triggering Timer and BPMN Error Events in Camunda Run

Video sau đây từ Camunda sẽ trình diễn cách thêm và cấu hình một Error Boundary Event trong Modeler. Hãy chú ý đến cách người trình bày định nghĩa một "Error Code".

Xem đoạn từ 09:16 đến 11:38. Tập trung vào các bước sau: Kéo một Boundary Event và gắn nó vào Service Task. Thay đổi loại sự kiện thành "Error". Trong bảng thuộc tính (Properties Panel), tìm đến mục "Error" và tạo một Error mới. Đặt tên cho Error (ví dụ: Help Refused) và quan trọng hơn là Error Code (ví dụ: refuse help). Đây chính là mã định danh mà chúng ta sẽ sử dụng trong code.

Khi lỗi nghiệp vụ xảy ra, sự kiện này sẽ "bắt" nó, hủy bỏ tác vụ đang chạy (theo mặc định) và chuyển hướng luồng quy trình theo đường dẫn mới mà bạn đã vẽ ra từ sự kiện đó.



3. Triển khai việc "Ném" Lỗi với handleBpmnError

Khi External Worker của bạn xác định rằng một tình huống nghiệp vụ đặc biệt đã xảy ra, nó cần thông báo cho process engine. Thay vì gọi complete() hoặc handleFailure(), bạn sẽ sử dụng phương thức ExternalTaskService.handleBpmnError().

External Tasks - Reporting BPMN Errors

Tài liệu chính thức của Camunda và một bài blog cung cấp các ví dụ code Java rõ ràng cho việc này. Hãy đọc để hiểu cách sử dụng phương thức handleBpmnError.

Tìm đến mục có tiêu đề tương tự như "a business error can appear during execution". Đọc kỹ phần mô tả và ví dụ code Java cho ExternalTaskService#handleBpmnError. Chú ý các tham số: errorCode, errorMessage, và variables.

Như tài liệu đã chỉ ra, đây là cú pháp của phương thức:
handleBpmnError(String taskId, String workerId, String errorCode, String errorMessage, Map<String, Object> variables)

Trong đó:

  • errorCode: Tham số quan trọng nhất. Nó phải khớp chính xác với Error Code mà bạn đã cấu hình trong Error Boundary Event trên sơ đồ BPMN.
  • errorMessage: Một thông báo tùy chọn để mô tả lỗi, bạn có thể lưu nó vào một biến quy trình để sử dụng sau này.
  • variables: Một Map chứa các biến mới hoặc cập nhật mà bạn muốn truyền vào quy trình. Điều này rất hữu ích để cung cấp thêm ngữ cảnh cho luồng xử lý lỗi.

Tiếp theo, hãy xem một đoạn code ví dụ rất súc tích từ bài blog "Camunda process orchestration with external Java tasks." (LINK).

// Giả sử một lỗi nghiệp vụ được phát hiện
externalTaskService.handleBpmnError(externalTask, "Simulated_Error");

Trong ví dụ đơn giản này, worker báo cáo một lỗi với mã Simulated_Error. Nếu có một Error Boundary Event được cấu hình để bắt mã lỗi này, nó sẽ được kích hoạt.

Để có cái nhìn trực quan hơn, hãy xem tiếp video trước đó, nơi người trình bày sửa đổi code của worker (lần này là Node.js, nhưng logic là tương tự) để ném lỗi mà họ vừa mô hình hóa.

Camunda Platform 7 Tutorial: Setting Up and Triggering Timer and BPMN Error Events in Camunda Run

Video này minh họa cách sửa đổi logic của worker để đưa ra quyết định nghiệp vụ và gọi phương thức tương đương với handleBpmnError.

Xem đoạn từ 11:38 đến 13:41. Chú ý vào khối if-else trong code. Nếu điều kiện nghiệp vụ không được đáp ứng, worker sẽ gọi một hàm xử lý lỗi BPMN, truyền vào errorCode là 'refuse help' - chính là mã lỗi đã định nghĩa trong Modeler.



4. Ví dụ Thực hành: Xử lý định dạng Video không được hỗ trợ

Hãy áp dụng những gì đã học vào worker VideoTranscodingHandler của chúng ta.

Kịch bản: Dịch vụ của chúng ta chỉ có thể chuyển mã các tệp .mp4 và .avi. Nếu người dùng tải lên một tệp .mov, đây là một lỗi nghiệp vụ. Chúng ta không nên thử lại, mà thay vào đó nên thông báo cho người dùng và kết thúc quy trình.

Bước 1: Sửa đổi sơ đồ BPMN

  1. Mở quy trình của bạn trong Camunda Modeler.
  2. Chọn Service Task "Transcode Video".
  3. Từ thanh công cụ bên cạnh, kéo và thả một Boundary Event lên biên của task.
  4. Nhấp vào sự kiện và sử dụng biểu tượng cờ lê để đổi loại thành Error Boundary Event.
  5. Trong bảng thuộc tính (Properties Panel) bên phải:
    • Trong phần "Details", đặt cho sự kiện một cái tên, ví dụ: "Unsupported Format".
    • Trong phần "Error", nhấp vào nút "New".
    • Đặt Name là UnsupportedFormatError.
    • Đặt Code là UNSUPPORTED_FORMAT. Đây là phần quan trọng nhất.
    • (Tùy chọn) Trong phần "Error Variable", nhập transcodingErrorMessage. Thao tác này sẽ lưu errorMessage mà worker gửi lên vào một biến quy trình có tên này.
  6. Từ Error Boundary Event, vẽ một luồng mới đến một User Task có tên "Inform User about Format". Sau đó, nối User Task này với một End Event.
  7. Deploy lại quy trình của bạn.

Bước 2: Sửa đổi VideoTranscodingHandler.java

Bây giờ, hãy cập nhật logic của worker để kiểm tra định dạng tệp và ném lỗi BPMN nếu cầ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.camunda.bpm.client.spring.annotation.ExternalTaskSubscription;
import java.util.Map;
// Các import khác giữ nguyên

@Component
@ExternalTaskSubscription(topicName = "video-transcoding", lockDuration = 30000)
public class VideoTranscodingHandler implements ExternalTaskHandler {

    private final static Logger LOGGER = LoggerFactory.getLogger(VideoTranscodingHandler.class);

    @Override
    public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) {
        String videoFile = externalTask.getVariable("videoFile");
        LOGGER.info("Bắt đầu xử lý video '{}'...", videoFile);

        try {
            // --- LOGIC XỬ LÝ LỖI NGHIỆP VỤ ---
            if (videoFile == null || (!videoFile.toLowerCase().endsWith(".mp4") && !videoFile.toLowerCase().endsWith(".avi"))) {
                String errorMsg = "Định dạng tệp không được hỗ trợ: " + videoFile;
                LOGGER.warn(errorMsg);
                
                // Ném lỗi BPMN về cho Engine
                externalTaskService.handleBpmnError(
                        externalTask,
                        "UNSUPPORTED_FORMAT", // Mã lỗi khớp với BPMN
                        errorMsg,             // Thông báo lỗi để lưu vào biến
                        Map.of("invalidFile", videoFile) // Truyền thêm biến nếu cần
                );
                return; // Kết thúc xử lý
            }
            // ------------------------------------

            // Logic xử lý thành công (giả lập)
            LOGGER.info("Giả lập chuyển mã video '{}'...", videoFile);
            Thread.sleep(5000); // Giả lập công việc
            LOGGER.info("Chuyển mã video '{}' thành công!", videoFile);

            Map<String, Object> outputVariables = Map.of("transcodedStatus", "SUCCESS");
            externalTaskService.complete(externalTask, outputVariables);

        } catch (Exception e) {
            // Giữ lại logic xử lý lỗi kỹ thuật từ bài học trước
            LOGGER.error("Xảy ra lỗi kỹ thuật!", e);
            Integer retries = externalTask.getRetries() == null ? 3 : externalTask.getRetries();
            externalTaskService.handleFailure(
                    externalTask,
                    "Lỗi kỹ thuật khi chuyển mã",
                    e.getMessage(),
                    retries - 1,
                    10000L
            );
        }
    }
}

Bước 3: Chạy và Quan sát

  1. Khởi chạy ứng dụng Spring Boot của bạn.
  2. Sử dụng Camunda Tasklist hoặc REST API để bắt đầu một quy trình mới với biến videoFile có giá trị là "my_vacation.mov".
  3. Quan sát trong Cockpit:
    • Bạn sẽ thấy process instance ngay lập tức đi theo đường dẫn lỗi và dừng lại ở User Task "Inform User about Format".
    • Kiểm tra các biến của quy trình, bạn sẽ thấy biến transcodingErrorMessage và invalidFile đã được tạo ra.
  4. Bây giờ, hãy thử bắt đầu một quy trình khác với videoFile là "project_demo.mp4". Bạn sẽ thấy nó đi theo luồng "happy path" và hoàn thành thành công.

Camunda Platform 7 Tutorial: Setting Up and Triggering Timer and BPMN Error Events in Camunda Run

Cuối cùng, hãy xem kết quả của việc chạy một quy trình với dữ liệu gây ra lỗi nghiệp vụ.

Xem đoạn từ 13:41 đến 15:15. Video cho thấy khi bắt đầu quy trình với dữ liệu không thỏa mãn điều kiện, luồng xử lý ngay lập tức đi theo nhánh lỗi đã được mô hình hóa. Đây chính là kết quả bạn sẽ thấy trong ví dụ thực hành của mình.



Tổng kết

Qua bài học này, bạn đã trang bị thêm một công cụ cực kỳ mạnh mẽ để thiết kế các quy trình nghiệp vụ linh hoạt và dễ đọc.

Những điểm chính cần ghi nhớ:

  • Lỗi Nghiệp vụ (BPMN Error) dùng để xử lý các kịch bản được dự đoán trước, không phải là sự cố kỹ thuật.
  • Việc xử lý lỗi nghiệp vụ bao gồm hai phần: "bắt" bằng Error Boundary Event trong BPMN và "ném" bằng handleBpmnError() từ External Worker.
  • Error Code là "chìa khóa" để kết nối giữa mô hình và mã nguồn.
  • handleBpmnError cho phép bạn không chỉ kích hoạt luồng lỗi mà còn truyền thêm thông tin ngữ cảnh qua errorMessage và variables.
  • Việc mô hình hóa các luồng xử lý lỗi nghiệp vụ giúp cho sơ đồ BPMN trở nên minh bạch và dễ hiểu hơn đối với cả đội ngũ kỹ thuật và nghiệp vụ.

Xem trước bài học tiếp theo

Chúng ta đã khám phá sâu về các tác vụ và cách xử lý lỗi bên trong một quy trình. Trong module tiếp theo, chúng ta sẽ mở rộng tầm nhìn sang một khía cạnh quan trọng khác của BPM: thời gian và giao tiếp. Bài học tới, "Sử dụng Timer Start Event để khởi tạo quy trình theo lịch trình", sẽ chỉ cho bạn cách tự động hóa việc khởi chạy quy trình vào những thời điểm cụ thể hoặc theo một chu kỳ lặp lại, mở ra nhiều khả năng tự động hóa hơn nữa.