Practical Symfony #28: Workflowerを使ったビジネスプロセスの管理
この記事はSymfony Advent Calendar 2015 25日目の記事です。前日の記事はqcmatsuokaさんの「SpBowerBundleのキャッシュエラーを解決する | QUARTETCOM TECH BLOG」でした。
WorkflowerはBPMN 2.0に準拠するPHP向けのワークフローエンジンであり、2条項BSDライセンスの下でリリースされているオープンソース製品です。Workflowerの主な用途としては、人間を中心としたビジネスプロセスをPHPアプリケーションで管理することが挙げられます。
この記事では、Workflowerを使ったビジネスプロセスの管理をSymfonyアプリケーション上で行うために必要な作業について示します。
PHPMentorsWorkflowerBundleによるSymfonyインテグレーション
PHPMentorsWorkflowerBundleはWorkflowerをSymfonyアプリケーションで使うためのインテグレーションレイヤーで、以下の機能を提供します。
- ワークフローに対応するDIコンテナサービスの自動生成と
phpmentors_workflower.process_aware
タグを使ったサービスオブジェクトの自動注入 - Symfonyセキュリティシステムを使った業務担当者(パーティシパント)の割り当てとアクセス制御
- Doctrine ORMを使ったエンティティのための透過的なシリアライゼーション・デシリアライゼーション
- 複数のワークフローコンテキスト(BPMNファイルが保存されたディレクトリ)
WorkflowerおよびPHPMentorsWorkflowerBundleのインストール
最初に、Composerを使ってWorkflowerおよびPHPMentorsWorkflowerBundleをプロジェクトの依存パッケージとしてインストールします。
$ composer require phpmentors/workflower "1.0.*" $ composer require phpmentors/workflower-bundle "1.0.*"
次に、PHPMentorsWorkflowerBundle
を有効にするためにAppkernel
を変更します。
... class AppKernel extends Kernel { public function registerBundles() { $bundles = array( ... new PHPMentors\WorkflowerBundle\PHPMentorsWorkflowerBundle(), ); ...
コンフィギュレーション
続いてPHPMentorsWorkflowerBundle
のコンフィギュレーションを行います。以下に例を示します。
app/config/config.yml:
phpmentors_workflower: serializer_service: phpmentors_workflower.base64_php_workflow_serializer workflow_contexts: app: definition_dir: "%kernel.root_dir%/../src/AppBundle/Resources/config/workflower"
serializer_service
- ワークフローのインスタンスとなるPHPMentors\Workflower\Workflow\Workflow
オブジェクトのシリアライズに使用するDIコンテナサービスのIDを指定します。指定されたサービスにはPHPMentors\Workflower\Persistence\WorkflowSerializerInterface
の実装が期待されています。デフォルトはphpmentors_workflower.php_workflow_serializer
です。また、シリアライズ済みのオブジェクトをBase64エンコード・デコードするphpmentors_workflower.base64_php_workflow_serializer
を使うこともできます。workflow_contexts
- ワークフローのコンテキストID毎にdefinition_dir
(BPMNファイルが保存されたディレクトリ)を指定します。
BPMNを使ったワークフローの設計
BPMN 2.0をサポートするエディターを使ってWorkflowerで動作させるワークフローを定義します。最初は開始イベント、タスク、終了イベントのみで構成されたワークフローを定義し、それでワークフローの開始から終了までの動作が確認できたら、改めてワークフロー全体を設計し、定義するとよいでしょう。このBPMNファイルの名前はワークフローID
(workflow ID
)として使われます。例えばLoanRequestProcess.bpmn
のような名前(キャメルケースを推奨)で保存します。分岐に使うシーケンスフローの条件式はSymfony ExpressionLanguageコンポーネントの式として評価されます。シーケンスフローの評価順は不定であるため、他の分岐先と整合的な条件式を設定する必要があることに注意してください。条件式の中では、PHPMentors\Workflower\Process\ProcessContextInterface::getProcessData()
から取得される連想配列のキーを使うことができます。
以下のスクリーンショットはEclipseで利用可能なBPMNエディターであるBPMN2 Modelerのものです。
Workflowerがサポートするワークフロー要素
WorkflowerはBPMN 2.0のワークフロー要素のうち以下のものをサポートしています。サポート外の要素はWorkflowerで動作させることができませんのでご注意ください。
-
接続オブジェクト(connecting objects)
- シーケンスフロー(sequence flows)
-
フローオブジェクト(flow objects)
-
アクティビティ(activities)
- タスク(tasks)
-
イベント(events)
- 開始イベント(start events)
- 終了イベント(end events)
-
ゲートウェイ(gateways)
- 排他ゲートウェイ(exclusive gateways)
-
アクティビティ(activities)
Note これらの要素はBPMNによるワークフロー定義を通してクライアントと共有されるワークフロードメインモデルを構成します。このドメインモデルはドメイン駆動設計(Domain-Driven Design: DDD)が提唱するユビキタス言語
(Ubiquitous Language
)の語彙となるものです。このようなドメインモデルを筆者はユビキタスドメインモデル(Ubiquitous Domain Model)と呼んでいます。ドメイン特化言語(Domain-Specific Language: DSL)はドメインとそのクライアントの間でユビキタスドメインモデル
を媒介する役割を担っているといえるでしょう。
ワークフローのインスタンスを表すエンティティの設計
特定のワークフローのインスタンス(Workflowerではプロセス
と呼ばれています)を表す永続化対象のエンティティを設計し、アプリケーションに追加します。このエンティティは通常PHPMentors\Workflower\Process\ProcessContextInterface
とPHPMentors\Workflower\Persistence\WorkflowSerializableInterface
を実装します。PHPMentors\Workflower\Process\ProcessContextInterface::getProcessData()
から返される連想配列は、シーケンスフローの条件式内で展開されます。
また、アプリケーションにおける必要性(例:データベースへの問い合わせ)に応じてWorkflow
オブジェクトのプロパティのスナップショットを保持するプロパティを用意するとよいでしょう。例えば、アプリケーションで特定のアクティビティに留まるプロセスをデータベースから検索する必要がある場合、現在のアクティビティを表す$currentActivity
プロパティをエンティティに追加します。以下に例を示します。
... use PHPMentors\Workflower\Persistence\WorkflowSerializableInterface; use PHPMentors\Workflower\Process\ProcessContextInterface; use PHPMentors\Workflower\Workflow\Workflow; ... class LoanRequestProcess implements ProcessContextInterface, WorkflowSerializableInterface { ... /** * @var Workflow */ private $workflow; /** * @var string * * @Column(type="blob", name="serialized_workflow") */ private $serializedWorkflow; ... /** * {@inheritdoc} */ public function getProcessData() { return array( 'foo' => $this->foo, 'bar' => $this->bar, ... ); } /** * {@inheritdoc} */ public function setWorkflow(Workflow $workflow) { $this->workflow = $workflow; } /** * {@inheritdoc} */ public function getWorkflow() { return $this->workflow; } /** * {@inheritdoc} */ public function setSerializedWorkflow($workflow) { $this->serializedWorkflow = $workflow; } /** * {@inheritdoc} */ public function getSerializedWorkflow() { if (is_resource($this->serializedWorkflow)) { return stream_get_contents($this->serializedWorkflow, -1, 0); } else { return $this->serializedWorkflow; } } ...
ビジネスプロセスの管理のためのドメインサービスの設計
Workflowerをプロダクションレベルで使うためには、プロセスの開始やワークアイテムの割り当て・開始・完了等を担当するドメインサービスが必要になるでしょう。特定のワークフローのプロセスとドメインサービスを結びつけるためにはPHPMentors\Workflower\Process\ProcessAwareInterface
を実装し、そのドメインサービスのDIコンテナサービスに対して、phpmentors_workflower.process_aware
タグを付与します。以下に例を示します。
... use PHPMentors\DomainKata\Usecase\CommandUsecaseInterface; use PHPMentors\Workflower\Process\Process; use PHPMentors\Workflower\Process\ProcessAwareInterface; use PHPMentors\Workflower\Process\WorkItemContextInterface; use PHPMentors\Workflower\Workflow\Activity\ActivityInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; ... class LoanRequestProcessCompletionUsecase implements CommandUsecaseInterface, ProcessAwareInterface { ... /** * @var Process */ private $process; ... /** * {@inheritdoc} */ public function setProcess(Process $process) { $this->process = $process; } ... /** * {@inheritdoc} */ public function run(EntityInterface $entity) { assert($entity instanceof WorkItemContextInterface); $this->process->completeWorkItem($entity); ...
対応するサービス定義は以下のようになります。
... app.loan_request_process_completion_usecase: class: "%app.loan_request_process_completion_usecase.class%" tags: - { name: phpmentors_workflower.process_aware, workflow: LoanRequestProcess, context: app } ...
この例のような「プロセスと操作の組み合わせ」に対応したユースケースクラスの実装は基本的ものといえますが、プロセス操作の共通性と可変性を分析し、可変性を外部に切り出すことができれば単一のクラスに操作をまとめることもできるでしょう。
ビジネスプロセスの管理
最後に、プロセスの開始やワークアイテムの割り当て・開始・完了等を実行するためのクライアント(コントローラー、コマンド、イベントリスナー等)を実装する必要があります。これらのクライアントも可変性を外部に切り出すことができるはずです。
ここまでの作業が終われば、Webインターフェイスやコマンドラインインターフェイス(Command Line Interface: CLI)からビジネスプロセスに対する一連の操作を実行できるようになります。
BPMSによるジェネレーティブプログラミングの実現に向けて
この記事では、Workflowerを使ったビジネスプロセスの管理をSymfonyアプリケーション上で行うために必要な作業について見てきました。WorkflowerおよびPHPMentorsWorkflowerBundleが提供するのはBPMN 2.0のワークフロー要素に対応するWorkflow
ドメインモデルと基本的なインテグレーションレイヤーに留まるため、実際にアプリケーションに組み込むためにはさらなる作業(とスキル)が要求されるでしょう。それは決して簡単なことではありません。なぜなら、それは対象ドメインに適したBPMS(Business Process Management System)あるいはBPMSフレームワークの設計に他ならないからです。
また、BPMSによるソフトウェア開発はビジネスプロセスドメインにおけるジェネレーティブプログラミング(Generative Programming)の実践といえます。PHPで利用可能なBPMSがほとんど存在しない現在、これに挑戦する者だけがその果実を手にすることができるのです。