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

【Activiti開発入門】ビジネスロジックの作成

概要

今回の入門記事では、Javaを利用して任意のビジネスロジックを記述できるJava Service Taskをご紹介します。Java Service Taskの詳細な仕様に関しては、「Activiti User Guide 8.5.3. Java Service Task」をご参照ください。 http://www.activiti.org/userguide/#bpmnJavaServiceTaskXML

f:id:lalalafrance:20161023141133p:plain

ステップ

  1. ビジネスロジックの実装

    JavaDelegateインターフェースを実装したクラスを作成し、ビジネスロジックを作成します。

  2. ビジネスロジックを呼び出すフローの作成

    1で作成したビジネスロジックを呼び出すService Taskをフロー上に配置します。

ビジネスロジックの作成

クラスパスの通し方

  1. 「Activiti Project」の作成

    BPMのフロー開発と同じく、ビジネスロジックもActiviti Project上で開発します。Activiti Designer(eclipse)上でActiviti Projectを作成します。

  2. Java Build Path」の設定

    作成したActiviti Projectに対してビルドパスを設定します。Activiti ProjectはMavenプロジェクトの形式になっていますが、eclipseMavenプロジェクトの形式としては不完全でMaven経由のビルドパスが通らないため、直接jarファイルをビルドパスに追加することでeclipse上のクラスパスを解決します。

    Activiti Project上で右クリックし、プロパティ画面を表示します。「Java Build Path」を設定し、「Add External JARS」を押下し、「activiti-engine-5.21.0.jar」を選択します。

    f:id:lalalafrance:20161023143254p:plain

ビジネスロジックの実装

  • 通常のMavenプロジェクトのような感じで、Javaクラスを追加します。クラスの追加時に、org.activiti.engine.delegate.JavaDelegateインターフェースを選択します。

    f:id:lalalafrance:20161023144808p:plain

  • クラス内に任意の業務処理を記述します。

package sample_java_service;

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 value = (String) execution.getVariable("sampleVar");
// 変数の更新
execution.setVariable("sampleVar", "aaa");
  • JavaDelegateのインスタンスはプロセス定義ごとにキャッシュされるため、同一のプロセス定義から生成されたプロセスインスタンス間で共有されます。スレッドセーフな実装になるようにしてください。

ビルド(パッケージング)とデプロイ

  • 作成したクラスを含むjarファイルをビルドします。Activiti ProjectはMavenプロジェクトの形式になっているので、Mavenのコマンドを実行するだけでjarファイルを作成可能です。

    f:id:lalalafrance:20161023145232p:plain

  • ビルドしたjarファイルは、Activitiを動作させるWebアプリケーションに同梱します。

  • Activitiのエンジンでは、独自のクラスローダを差し込む仕組みも提供しているため、例えばActiviti Projectから生成したデプロイメントにクラスを同梱してデプロイするような仕組みも作成可能ですが、今回は紹介しません。 http://activiti.org/javadocs/org/activiti/engine/ProcessEngineConfiguration.html#setClassLoader-java.lang.ClassLoader-

ビジネスロジックを呼び出すフローの作成

ここでは、「ビジネスロジックの実装」で作成したクラスを呼び出すだけのフローの作成方法をご紹介します。

  1. Activiti Diagramを作成し、フローのデザイナー画面を表示します。

  2. 以下のアイコンを配置します。

    • 「Start Event」カテゴリから「StartEvent」を配置します。
    • 「Task」カテゴリから「Service Task」を配置します。
    • 「End Event」カテゴリから「EndEvent」を配置します。
  3. 以下のように線を引きます。

    • 「StartEvent」から「Service Task」へ線を引きます。
    • 「Service Task」から「EndEvent」へ線を引きます。
  4. 起票者を設定します。

    • デザイナー上のアイコン以外の箇所をクリックします。
    • Propertiesタブの「Candidate start users」に任意のユーザを設定します。
  5. Service taskを設定します。

    • デザイナー上の「Service Task」のアイコンをクリックします。
    • 「Main config」タブのClass name属性に「ビジネスロジックの実装」で作成したクラス名を設定します。

    • 「General」タブのAsynchronous属性にて同期・非同期の設定を行います。デフォルトの設定では、Service Taskで設定したJavaクラスの処理はフロー上で手前に配置されているアクティビティと同一のスレッドから呼び出されます。例えば、User Taskの直後にService Taskを配置し、同期の設定にした場合は、User Taskを操作しているユーザは、Service Taskの処理の完了を待ちます。Service Taskで時間がかかる処理を行う場合には、Asynchronous属性をオンにして、処理が非同期スレッドにて実行されるようにしてください。

  6. フローを保存・ビルドし、出力されたbarファイルをActiviti Explorerにデプロイします。

サンプルフロー

ユーザタスクで入力した文字列が数値形式かどうかチェックして、数字でない場合はエラーを返すだけのサンプルを以下のページで公開しています。 https://github.com/daisuke-yoshimoto/sample_java_service

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

[追記]

JavaDelegateインターフェースを実装したクラスは、Service Taskに対して1インスタンスのみ生成され、全てのプロセスインスタンスの間で共有されます。そのため、スレッドセーフに実装する必要があります。

【Activiti EL式】リスナーのEL式でプロセスの変数にシステムパラメータを書き込む

(例)プロセスインスタンスIDを変数に格納する。

1. 開始イベントのリスナーに以下を追加する。

  • イベント「start」

  • タイプ「式」

${execution.setVariable("process_instance_id", execution.getProcessInstanceId())}

2. プロセスの開始時に変数にプロセスインスタンスIDが格納される。

【Activitiはまりポイント】デフォルトフローの仕様

Inclusive GatewayやExclusive Gatewayの設定でデフォルトフローが設定できるが、こちらで選択したフローは他のフローが選出されない場合有効である。 デフォルトフローの条件は無視される。

http://www.activiti.org/userguide/#bpmnDefaultSequenceFlow

【Activiti入門】並行ルートの作成

概要

前回の入門記事では条件分岐のルートを紹介しましたが、今回は並行ルートを扱います。 並行ルートとは、同時に異なるユーザタスクが割り当てられるルートのことです。

f:id:lalalafrance:20160428212458p:plain

ステップ

  1. 並行ルートを実現するには、「ParallelGateway」のアイコンを利用します。

    • Gateway」カテゴリにある「ParallelGateway」を利用することで、並行なルートを引くことが可能です。

      http://www.activiti.org/userguide/#bpmnParallelGateway

    • 「ParallelGateway」から各「UserTask」へのシーケンスを引くことで、各「UserTask」ごとに並列にプロセスが動作します。

    • 各「UserTask」から「ParallelGateway」へシーケンスを引くことで、並行に実行されていたプロセスが結合します。各プロセスが完了するまで待ち合わせを行います。

フローの作成

では、実際に並行ルートを実現するフローを作成していきます。

  1. Activiti Diagramを作成し、フローのデザイナー画面を表示します。

  2. 以下のアイコンを配置します。

    • 「Start Event」カテゴリから「StartEvent」を配置します。
    • Gateway」カテゴリから「ExclusiveGateway」を配置します。
    • 「Task」カテゴリから「UserTask」を2つ配置します。
    • Gateway」カテゴリから「ExclusiveGateway」を配置します。
    • 「End Event」カテゴリから「EndEvent」を配置します。
  3. 以下のように線を引きます。

    • 「StartEvent」から「ParallelGateway」へ線を引きます。
    • 「ParallelGateway」から2つの「UserTask」へそれぞれ線を引きます。
    • 2つの「UserTask」からそれぞれ「ParallelGateway」へ線を引きます。
    • 「ParallelGateway」から「EndEvent」へ線を引きます。
  4. 起票者を設定します。

    • デザイナー上のアイコン以外の箇所をクリックします。
    • Propertiesタブの「Candidate start users」に「fozzie」を設定します。
  5. 処理対象者を設定します。

    • デザイナー上のユーザタスクの1つ目をクリックします。
    • Propertiesタブの「Main config」-「Assignee」に「kermit」を設定します。
    • デザイナー上のユーザタスクの2つ目をクリックします。
    • Propertiesタブの「Main config」-「Assignee」に「gonzo」を設定します。
  6. フローを保存・ビルドし、出力されたbarファイルをActiviti Explorerにデプロイします。

フローの実行

  1. fozzie/fozzieでActiviti Explorerへログインします。

  2. 「プロセス」からデプロイしたフローを選択します。

  3. 「プロセスの開始」を押下し、プロセスを開始します。

  4. kermit/kermitでActiviti Explorerへログインします。

  5. 「ケース」-「受信トレイ」にタスクがあります。

  6. 「タスクの完了」を押下し、タスクを完了させます。

  7. gonzo/gonzoでActiviti Explorerへログインします。

  8. 「ケース」-「受信トレイ」にタスクがあります。

  9. 「タスクの完了」を押下し、タスクを完了させます。

  10. fozzie/fozzieでActiviti Explorerへログインします。

  11. 「プロセス」-「マイ・インスタンス」から開始したフローが完了していることを確認します。

f:id:lalalafrance:20160428212516p:plain

注意

  • ParallelGatewayに接続されるシーケンスの条件は、評価されません。条件の設定は、無視されます。

[追記]

この記事では、並列に動作しているプロセスをParallelGatewayで再度結合し待ち合わせを行っていますが、結合しない場合は各プロセスが終了まで並列に動作します。

f:id:lalalafrance:20170416003923p:plain

【Activiti入門】変数

概要

  • Activitiのプロセスでは、変数を利用してプロセスに関する入力データを扱う。

  • 変数はデータベース(テーブル ACT_RU_VARIABLE)にて管理される。

    ただし、変数の値にJava beanなどが格納された場合は、バイナリデータとしてACT_GE_BYTEARRAYテーブルに格納される。

  • 変数には、スコープが存在する。

  • 参考

    Activiti User Guide 4.5. Variables

Process Variables

  • プロセス全体で有効となる変数。

    レコードとしては、テーブル ACT_RU_VARIABLEのカラムproc_inst_idとカラムexecution_idが一致するもの。

Execution

  • アクティブなExecution内でのみ有効な変数。

    説明が難しいが、Activitiでは、プロセスインスタンス上で現在アクティブな箇所をExecutionと定義している。 例えば、以下のような並行ルートの場合は、並行ルートの処理中に3つのアクティブなExecutionが存在する。 1つ目はプロセスインスタンス全体のExecution、2,3つ目はそれぞれの並行ルートのExecutionである。

    f:id:lalalafrance:20160419175932p:plain

  • Executionスコープの変数とは、プロセスインスタンスのExecutionではない、個々のExecution内で有効となる変数である。

    レコードとしては、テーブル ACT_RU_VARIABLEのカラムproc_inst_idとカラムexecution_idが一致しないもの。

UserTask Variables

  • ユーザタスク内でのみ有効な変数。

    レコードとしては、テーブル ACT_RU_VARIABLEのカラムtask_id_が空でないもの。

履歴

  • 変数の履歴は、テーブル ACT_HI_VARINSTに格納される。