Activiti日本語情報ブログ

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

【告知】アドベントカレンダーはじめました(Activiti Advent Calendar 2017)

Activiti についてのアドベントカレンダーをはじめました。 qiita.com

現在開発を進めているActiviti 7の情報を日本語で発信するというところを主にやっていきたいと思っていますが、Activiti自体がニッチなこともあり、前半は入門的な内容と既存のバージョン(Activiti 5,6)の話を中心に進めていきたいと思います。

GitHubのスター数で見るBPMエンジン

はてなブログでフォローしてる方の記事を真似して、BPMエンジン版で書いてみました。 takezoe.hatenablog.com

まず、トップ5の選出ですが、シンプルにbpmで検索してスター数でソートしました。ただし、5つ目については、私の独断と偏見でbonitasoftを持ってきてます。本来であれば、Python製のfaxad/ActivFlowがきそうですね。

続いて、元記事と同じように、star-historyを使ってトップ5のスター数の推移を出してみました。camundaとbpmn-ioは合算してイメージしてください。 f:id:lalalafrance:20171119222528p:plain

注意書き

各プロダクトに関するコメントは、公正中立なものではありません。というのも、私自身がActivitiの関係者なのでポジショントークになってしまうのと、正直私がちゃんと触ったことがあるのはActivitiだけだからです。

Activiti/Activiti ★ 2803

github.com

ダントツの人気トップは、本ブログでも扱っているActivitiです。BPMN 2.0の仕様にそったプロセスを描けるEclipseベースのActiviti Designerと、BPMN 2.0の実行エンジンであるActiviti Engineがコアプロダクトです。既存のJavaアプリケーションに組み込んで使うだけでなく、外立てしたActiviti EngineとREST APIベースで連携できるActiviti RESTも提供されています。こちらは、Spring MVCをベースにしています。

今年は、ついにActiviti 6という初期バージョン(Activiti 5)の後継の正式版がリリースされました。アドホックなサブプロセスやDMN(Decision Model and Notation)エンジンの機能が追加されています。Designerに関しては、Eclipseを脱却し、WebベースのActiviti 6 App UIがリリースされました。

今後に関して言うと、さらに後継のバージョン Activiti 7の開発が進んでおり、こちらではMicroservicesへの対応とCMMN(Case Management Model and Notation)のサポートを目指しています。

camunda/camunda-bpm-platform ★ 484

bpmn-io/bpmn-js ★ 1017

github.com github.com

camundaは元々CamundaというBPM専門のソフトウェアコンサルタント会社がActivitiをフォークして始めたプロジェクトです。テーブル名なんかは未だにActivitiのプレフィックス(act_)が残っていますが、独自に進化も遂げていて、CMMNをサポートしたエンジンも提供しています。

また、プロセスのモデリングツールに関しては、Activitiよりクオリティの高いものを出しており、bpmn-ioという別プロジェクトとして立ち上げています。bpmn-ioは、この分野では中々のシェアを占めていると思います。むしろ、スター数を見ると、bpmn-ioはcamundaを上回っています。

https://camunda.org/assets/img/camunda-modeler/overview-bpmn-properties-panel.png

kiegroup/jbpm ★ 712

github.com

jbpmは、RedhatJBoss BPMのことです。こちらは長い歴史があるBPMエンジンで、最初に紹介したActivitiは、こちらの後継・刷新を目指すものとして始まったプロジェクトです。

flowable/flowable-engine ★ 446

flowableは、元々Activitiのリード開発者だったJoram BarrezとTijs RademakersがActivitiをフォークして昨年始めたプロジェクトです。フォークする際にメインコミッターをほとんど連れて行ったので、そう意味では正当なActivitiの後継かもしれません。

現時点で、Activitiとの大きな違いとしては、こちらは早くもCMMNをサポートしている点です。この10月にCMMN 1.1というCase Managementの標準仕様をサポートしたバージョンをリリースしています。

github.com

bonitasoft/bonita-engine ★ 53

github.com

[参考] Bossie Awards: The best open source applications

Githubのスター数と別の指標として米IDGの出版部門InfoWorldの編集者がその年の優れたオープンソースソフトウェアを選ぶ「Bossie Awards」というものがあります。こちらの指標だと、2013年時点ではActivitiとbonitasoftが選出されていますが、昨年の時点ではActivitiをforkしたcamundaが選出されていますね。

  • Bossie Awards 2016: The best open source applications
  • Bossie Awards 2015: The best open source applications
  • Bossie Awards 2014: The best open source applications
  • Bossie Awards 2013: The best open source applications

【Activiti Tips】プロセス図を出力する

概要

今回の記事では、Activiti Designerで作成したbpmnファイルを元にサーバサイドでプロセス図の画像ファイルを出力する方法をご紹介します。

プロセス図(プロセス定義)

RepositoryService.getProcessDiagram(String processDefinitionId)を利用することで、プロセス定義の静的なプロセス図を出力することが可能です。 プロセス図はInputStreamで返ってくるので、適宜ファイルへ出力してください。 https://www.activiti.org/javadocs/org/activiti/engine/RepositoryService.html#getProcessDiagram-java.lang.String-

f:id:lalalafrance:20170601223430p:plain

プロセス図(プロセスインスタンス

稼働中のプロセスのプロセス図を出力するには、インターフェース ProcessDiagramGeneratorを利用します。 実装は、ProcessEngineConfiguration().getProcessDiagramGenerator()で取得できます。

プロセス定義と違って、プロセスインスタンスの場合はアクティビティを指定してプロセス図を出力します。 プロセス図はInputStreamで返ってくるので、適宜ファイルへ出力してください。

// プロセス定義のBPMNモデルを取得
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDef.getId());

// プロセス図を出力
ProcessDiagramGenerator diagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream processInstanceDiagramStream = diagramGenerator.generateDiagram(bpmnModel, "png", runtimeService.getActiveActivityIds(processInstance.getId()));

f:id:lalalafrance:20170601223456p:plain

サンプルプログラム

プロセス定義, プロセスインスタンスそれぞれのプロセス図を出力するサンプルプログラムを以下のリポジトリで公開しています。

https://github.com/daisuke-yoshimoto/generateDiagram

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

【Activiti EL式】プロセスインスタンスの開始年を取得する

  • EL式と言っても、もはやコーディングしているような感じですが。。。

プロセスインスタンスの開始年を取得する

  • 以下のように暗黙オブジェクト executionからプロセスエンジンを取得してクエリでプロセスインスタンスを持ってくれば、プロセスの開始日時を解決できる。
  • getStartTime()の戻り値がjava.util.Dateなので、getYear()を実行すると、プロセスインスタンスの開始年を取得できる。
${execution.getEngineServices().getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult().getStartTime().getYear()+1900}

利用例

  • タイマーの中間イベントなどでプロセス開始年の特定の日付まで待たせる際などに利用できる。
  • 以下をタイマー中間イベントのTime date(ISO 8601)に設定すると、プロセス開始年の10月1日なったら自動で次に進むように利用できる。
${execution.getEngineServices().getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult().getStartTime().getYear()+1900}-10-01

f:id:lalalafrance:20170531210413p:plain

【Activiti Tips】実行中のプロセスインスタンスが参照するプロセス定義を差し替える

概要

今回の記事では、誤って不具合を含んだプロセス定義をリリースして運用を始めてしまった場合に、稼働中のプロセスインスタンスの参照するプロセス定義を変更する2つの方法を紹介します。

1つ目の方法は、不具合を修正したプロセス定義を作成し、再度デプロイを実施することです。 もう一つの方法は、すでにサーバ上にデプロイされた不具合を含んだプロセス定義を直接編集する方法です。

修正したプロセス定義の再デプロイ

Activitiの内部コマンド SetProcessDefinitionVersionCmdを利用して、プロセスインスタンスが参照しているプロセス定義のバージョンを変更することが可能です。 不具合を修正したプロセス定義をデプロイし、SetProcessDefinitionVersionCmdを利用して既存のプロセスインスタンスが再デプロイしたプロセス定義を参照するようにします。

ステップ

  1. 修正したプロセス定義をデプロイします

    不具合を修正したプロセス定義をデプロイし、デプロイ後のバージョンを確認します。

  2. SetProcessDefinitionVersionCmdの実行

    SetProcessDefinitionVersionCmdを利用して既存のプロセスインスタンスが参照するプロセス定義を、ステップ1でデプロイされたバージョンに差し替えます。SetProcessDefinitionVersionCmdには対象となるプロセスインスタンスのIDと差し替え対象のプロセス定義のバージョンを指定します。

   ((ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration()).getCommandExecutor().
        execute(
            new SetProcessDefinitionVersionCmd(processInstanceId, processDefinitionVersion)
        );

プロセス定義の直接変更

ActivitiのAPI DynamicBpmnServiceを利用し、既存のプロセスインスタンスが参照するプロセス定義を直接書き換えます。

ステップ

  • DynamicBpmnServiceのchange〜メソッドを利用して、変更内容を表すObjectNodeを作成します。
// シーケンスの分岐条件の内容を変更しています。
ObjectNode changedNode = processEngine.getDynamicBpmnService().changeSequenceFlowCondition("flow4", "${input == 'aaa'}");
  • DynamicBpmnServiceのsaveProcessDefinitionInfoメソッドを利用して、変更内容をプロセス定義へ反映します。
processEngine.getDynamicBpmnService().saveProcessDefinitionInfo(processInstance.getProcessDefinitionId(), changedNode);

サンプルプログラム

SetProcessDefinitionVersionCmd, DynamicBpmnServiceそれぞれを利用したサンプルプログラムを以下のリポジトリで公開しています。

https://github.com/daisuke-yoshimoto/process_def_migration_sample

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

【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インスタンスのみ生成され、全てのプロセスインスタンスの間で共有されます。そのため、スレッドセーフに実装する必要があります。