Activiti日本語情報ブログ

OSSのBPMエンジン Activitiの日本語情報をまとめています。

【Activiti開発入門】ビジネスロジックにおけるエラーハンドリング(1) - エラーのスローとキャッチ

概要

今回の入門記事では、Service Taskのビジネスロジックで発生したエラー(例外)のハンドリング方法についてご紹介します。

エラーハンドリングは、大きく2パターンに分かれています。1つ目は、発生したエラーをビジネスエラー(業務上のエラー)として扱い、業務プロセス上でその対応を行う方法です。2つ目は、単に一時的なシステムエラーとして扱い、リトライして対応する方法です。

前者は注文された商品の在庫切れなど業務ルールと合致しないケースが発生し、それに対応するをフローを記述する場合に利用します。後者は、データベースとの接続エラーなど技術的な原因に起因したエラーが発生した際に利用されます。

[コラム]システムエラーとビジネスエラー

Activitiでは、システムエラーとビジネスエラーを明確に区別し、ビジネスエラーを業務プロセス上で表現する方針にしています。これに関して、Activitiのフォーラムでは、以下のような見解が述べられています。

We've discussed this internally when we implemented the error boundary event, and we came to the conclusion that the error event is meant for errors with a 'process' meaning. A Java exception is something that always will be implemented by a developer, so that stuff needs to be hidden from the common model between devs and analysts. However if you want the semantics you write, you could catch your exception and call the ErrorEndEventActivityBehavior yourself.

我々はエラー境界イベントを実装する際に内部で議論し、エラーイベントはプロセスに関連するエラーを表すものであるという結論に至った。Javaの例外は開発者によって実装されるものなので、開発者とアナリストの間で共通となるモデルからは隠蔽されなければならない。

一方、BPMN2.0の仕様自体はもっとも簡素なもので、そもそもシステムエラーとビジネスエラーを区別していません。他のBPM製品(Oracle BPM)ではシステムエラーに関しても業務フロー上で明示的に表現させる方針の製品も存在します。

ビジネスエラー(業務エラー)のハンドリング

ビジネスロジックで発生したエラーを業務プロセス上で扱うには、JavaDelegateクラスからBpmnErrorをスローします。BpmnErrorをスローすることでエラーイベントが発生し、業務プロセス上のエラー境界イベントもしくはイベントサブプロセスでエラーイベントに対する制御フローを記述することが可能です。

  1. エラーイベントの発生(BpmnErrorのスロー)

  2. エラーイベントに対する制御フローの作成

(参考)Activiti User Guide https://www.activiti.org/userguide/#serviceTaskExceptionHandling

エラーイベントの発生(BpmnErrorのスロー)

エラーイベントを発生させるために、ビジネスロジック(JavaDelegateクラス)の内部からorg.activiti.engine.delegate.BpmnErrorクラスをスローします。

https://www.activiti.org/javadocs//org/activiti/engine/delegate/BpmnError.html

エラーのパターンが複数あり、それぞれを業務プロセス上で区別したい場合は、BpmnErrorクラスのコンストラクタにてエラーコードを指定します。指定したエラーコードは、エラーイベントのエラーコードとなります。

import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class SampleJavaDelegate implements JavaDelegate {

    @Override
    public void execute(DelegateExecution execution) throws Exception {
        String inputVar = (String) execution.getVariable("input_var");
        if("error1".equals(inputVar)){
            // エラーのパターンに応じたエラーコードを指定します。
            throw new BpmnError("Error_Code1");
        }else if("error2".equals(inputVar)) {
            // エラーのパターンに応じたエラーコードを指定します。
            throw new BpmnError("Error_Code2");
        }
        
    }

}

エラーイベントに対する制御フローの作成

発生したエラーイベントをキャッチし、制御するフローを業務プロセス上に記述します。ビジネスロジックを実行するサービスタスクに対して、エラー境界イベントもしくはエラー開始イベントから始まるイベントサブプロセスを配置し、サービスタスクから発生したエラーイベントに対する制御を記述します。

エラー境界イベント f:id:lalalafrance:20161226003817p:plain

エラー開始イベントから始まるイベントサブプロセス

f:id:lalalafrance:20161226003754p:plain

エラーイベントのパターンが複数存在する場合は、キャッチするエラー境界イベント・エラー開始イベントを各パターンごとに複数配置し、エラーイベントのエラーコードとのひもづけをキャッチするエラー境界イベント・エラー開始イベントのMain configのError code属性に指定します。

サンプルフロー

ユーザの入力ごとに2つのエラーハンドリングのパターンを実装したプロセスのサンプルを以下のページで公開しています。 https://github.com/daisuke-yoshimoto/sample_java_error_handling

上記のリポジトリのサンプルコードはシンプルさを重視しています。 実際の開発・運用において必ずしも適切な実装ではありません。参考にされる場合は、十分ご注意ください。

システムエラーのハンドリング

ビジネスロジック(JavaDelegateクラス)からExceptionクラスのサブクラスをスローした場合は、システムエラーが発生したとしてActivitiに判断され、リトライされます。リトライのパターンは、サービスタスクが同期(Asynchronous属性が無効)か非同期(Asynchronous属性が有効)かの設定によって異なります。

サービスタスクが同期(Asynchronous属性が無効)で実行される場合

ビジネスロジック(JavaDelegateクラス)から例外がスローされ、システムエラーが検知されると、一つ前のタスクまで戻ります(トランザクション全体がロールバックされる)。

サービスタスクが非同期(Asynchronous属性が有効)で実行される場合

非同期の設定の場合、サービスタスクはジョブとして非同期スレッドで実行されます。この状態では、一つ前のタスクまでのトランザクションはすでに確定しているため、サービスタスクのジョブ内でシステムエラーが発生した場合でも一つ前のタスクまで戻ることはありません。

ジョブが非同期スレッドにて、3度リトライされます。3度のリトライでもエラーが発生した場合は、ジョブテーブルに格納され、Activiti Explorerのジョブ一覧から手動で再実行できます。Activiti Explorer以外の場合は、APIから再実行する必要があります。

(参考)Activiti User Guide

http://www.activiti.org/userguide/#asyncContinuations http://www.activiti.org/userguide/#failRetry