Springを使用してシンプルなワークフローエンジンを作成する

多くのJaveエンタープライズアプリケーションでは、メインシステムとは別のコンテキストで処理を実行する必要があります。多くの場合、これらのバックエンドプロセスはいくつかのタスクを実行し、一部のタスクは前のタスクのステータスに依存します。相互に依存する処理タスクが必要な場合、通常、単一の手続き型スタイルのメソッド呼び出しのセットを使用した実装では不十分であることがわかります。Springを利用すると、開発者はバックエンドプロセスをアクティビティの集合体に簡単に分離できます。Springコンテナはこれらのアクティビティを結合して、単純なワークフローを形成します。

この記事の目的上、単純なワークフローは、ユーザーの操作なしに所定の順序で実行される一連のアクティビティとして定義されます。ただし、このアプローチは、既存のワークフローフレームワークの代わりとしては推奨されていません。フォーク、参加、ユーザー入力に基づく移行など、より高度な対話が必要なシナリオでは、スタンドアロンのオープンソースまたは商用ワークフローエンジンの方が適しています。あるオープンソースプロジェクトは、より複雑なワークフロー設計をSpringとうまく統合しました。

手元のワークフロータスクが単純な場合、特にSpringがすでに使用されている場合は、完全に機能するスタンドアロンのワークフローフレームワークとは対照的に、単純なワークフローアプローチが理にかなっています。これは、立ち上げ時間を発生させることなく迅速な実装が保証されるためです。さらに、Springの軽量の制御の反転コンテナの性質を考えると、Springはリソースのオーバーヘッドを削減します。

この記事では、プログラミングのトピックとしてワークフローを簡単に紹介します。ワークフローの概念を使用して、Springはワークフローエンジンを駆動するためのフレームワークとして採用されています。次に、実稼働展開オプションについて説明します。ワークフローのデザインパターンと関連する背景情報に焦点を当てて、単純なワークフローのアイデアから始めましょう。

シンプルなワークフロー

モデリングワークフローは1970年代まで研究されてきたトピックであり、多くの開発者が標準化されたワークフローモデリング仕様の作成を試みてきました。ワークフローパターン、WHM van derAalst他によるホワイトペーパー。(2003年7月)は、最も一般的なワークフローシナリオを正確にモデル化する一連のデザインパターンの分類に成功しました。ワークフローパターンの中で最も些細なものの中には、シーケンスパターンがあります。単純なワークフローの基準に適合するシーケンスワークフローパターンは、シーケンスで実行される一連のアクティビティで構成されます。

UML(Unified Modeling Language)アクティビティ図は、ワークフローをモデル化するメカニズムとして一般的に使用されます。図1は、標準のUMLアクティビティ図を使用してモデル化された基本的なシーケンスワークフロープロセスを示しています。

シーケンスワークフローは、J2EEアプリケーションで普及している標準のワークフローパターンです。J2EEアプリケーションでは通常、一連のイベントがバックグラウンドスレッドで発生するか非同期で発生する必要があります。図2のアクティビティ図は、関心のある旅行者にお気に入りの目的地への航空運賃が下がったことを通知するための簡単なワークフローを示しています。

図1の航空会社のワークフローは、動的な電子メール通知の作成と送信を担当します。プロセスの各ステップはアクティビティを表します。ワークフローを開始する前に、何らかの外部イベントが発生する必要があります。この場合、そのイベントは航空会社のフライトルートのレート低下です。

航空会社のワークフローのビジネスロジックを見ていきましょう。最初のアクティビティでレート低下通知に関心のあるユーザーが見つからない場合、ワークフロー全体がキャンセルされます。関心のあるユーザーが見つかった場合、残りのアクティビティは完了します。続いて、XSL(Extensible Stylesheet Language)変換によってメッセージの内容が生成され、その後、監査情報が記録されます。最後に、SMTPサーバーを介してメッセージを送信しようとします。送信がエラーなしで完了すると、成功がログに記録され、プロセスが終了します。ただし、SMTPサーバーとの通信中にエラーが発生した場合は、特別なエラー処理ルーチンが引き継ぎます。このエラー処理コードは、メッセージの再送信を試みます。

航空会社の例を考えると、1つの質問が明らかです。どのようにして、一連のプロセスを個々のアクティビティに効率的に分割できますか?この問題は、Springを使用して雄弁に処理されます。制御の反転フレームワークとしてのSpringについて簡単に説明しましょう。

反転制御

Springでは、この責任をSpringコンテナに移動することで、オブジェクトの依存関係を制御する責任を取り除くことができます。この責任の移転は、制御の反転(IoC)または依存性注入として知られています。IoCと依存性注入の詳細については、MartinFowlerの「制御の反転と依存性注入パターン」(martinfowler.com、2004年1月)を参照してください。Springは、オブジェクト間の依存関係を管理することにより、クラスを相互にコラボレーションさせることのみを目的として記述されたコードであるグルーコードの必要性を排除します。

SpringBeanとしてのワークフローコンポーネント

行き過ぎる前に、今が春の背後にある主要な概念を説明する良い機会です。ApplicationContextインターフェースは、継承BeanFactoryインターフェイス、スプリング内の実際の制御エンティティまたはコンテナとして自身を課します。ApplicationContext知られている豆のセットのインスタンス化、構成、およびライフサイクル管理を担当して春の豆。ApplicationContextで構成されているアップ配線XMLベースの設定ファイルでのSpring Beanを。この構成ファイルは、SpringBeanが相互にコラボレーションする性質を示します。したがって、Springの話では、他の人と相互作用するSpringBeanはコラボレーターと呼ばれます。デフォルトでは、SpringBeanはシングルトンとして存在します。ApplicationContextただし、シングルトン属性をfalseに設定すると、Springがプロトタイプモードと呼ぶもので動作するように効果的に変更できます。

例に戻ると、航空運賃の値下げでは、SMTP送信ルーチンの抽象化が、ワークフロープロセスの例の最後のアクティビティとして配線されています(サンプルコードは「参考文献」で入手できます)。5番目のアクティビティであるため、このBeanは適切な名前が付けられていactivity5ます。メッセージを送信するにactivity5は、デリゲートコラボレーターとエラーハンドラーが必要です。

ワークフローコンポーネントをSpringBeanとして実装すると、2つの望ましい副産物、単体テストの容易さと高度な再利用性が得られます。IoCコンテナの性質を考えると、効率的な単体テストは明らかです。SpringのようなIoCコンテナーを使用すると、コラボレーターの依存関係をテスト中にモック置換と簡単に交換できます。航空会社の例では、のActivityようなactivity5SpringBeanはスタンドアロンテストから簡単に取得できますApplicationContext。モックSMTPデリゲートをに置き換えると、activity5単体テストをactivity5個別に行うことができます。

2番目の副産物である再利用性は、XSL変換などのワークフローアクティビティによって実現されます。ワークフローアクティビティに抽象化されたXSL変換は、XSL変換を処理するすべてのワークフローで再利用できるようになりました。

ワークフローの配線

提供されているAPI(Resourcesからダウンロード可能)では、Springは、ワークフローを構成する方法で対話するために、プレーヤーの小さなセットを制御します。主なインターフェースは次のとおりです。

  • Activity:ワークフロープロセスの単一ステップのビジネスロジックをカプセル化します。
  • ProcessContext:タイプのオブジェクトはProcessContext、ワークフローのアクティビティ間で渡されます。このインターフェイスを実装するオブジェクトは、ワークフローが1つのアクティビティから次のアクティビティに移行するときに状態を維持する役割を果たします。
  • ErrorHandler:エラーを処理するためのコールバックメソッドを提供します。
  • Processor:メインワークフロースレッドの実行者として機能するBeanについて説明します。

サンプルコードからの次の抜粋は、単純なワークフロープロセスとして航空会社の例をバインドするSpringBean構成です。

             /property>  org.iocworkflow.test.sequence.ratedrop.RateDropContext  

このSequenceProcessorクラスは、シーケンスパターンをモデル化する具象サブクラスです。プロセッサに接続されているのは、ワークフロープロセッサが順番に実行する5つのアクティビティです。

ほとんどの手続き型バックエンドプロセスと比較すると、ワークフローソリューションは、非常に堅牢なエラー処理が可能であるという点で非常に際立っています。エラーハンドラは、アクティビティごとに個別に配線できます。このタイプのハンドラーは、個々のアクティビティレベルできめ細かいエラー処理を提供します。アクティビティにエラーハンドラーが配線されていない場合、ワークフロープロセッサー全体に定義されたエラーハンドラーが問題を処理します。この例では、ワークフロープロセス中に未処理のエラーが発生すると、そのエラーは伝播してErrorHandlerBeanによって処理され、defaultErrorHandlerプロパティを使用して接続されます。

より複雑なワークフローフレームワークは、遷移間で状態をデータストアに保持します。この記事では、状態遷移が自動で行われる単純なワークフローの場合にのみ関心があります。状態情報はProcessContext、実際のワークフローの実行中にのみ利用できます。2つの方法しかないので、ProcessContextインターフェースがダイエット中であることがわかります。

public interface ProcessContext extends Serializable { public boolean stopProcess(); public void setSeedData(Object seedObject); }

ProcessContext航空会社のサンプルワークフローに使用される具体的なクラスはRateDropContextクラスです。このRateDropContextクラスは、航空会社の料金引き下げワークフローを実行するために必要なデータをカプセル化します。

これまで、すべてのBeanインスタンスは、デフォルトApplicationContextの動作に従ってシングルトンでした。ただしRateDropContext、航空会社のワークフローを呼び出すたびに、クラスの新しいインスタンスを作成する必要があります。この要件を処理するために、SequenceProcessorは、完全修飾クラス名をprocessContextClassプロパティとして使用して構成されます。ワークフローを実行するたびに、は指定されたクラス名を使用してSpringからのSequenceProcessor新しいインスタンスを取得しProcessContextます。これが機能するためには、非シングルトンのSpring Beanまたはタイプのプロトタイプがにorg.iocworkflow.test.sequence.simple.SimpleContext存在する必要がありますApplicationContextrateDrop.xmlリスト全体を参照)。

ワークフローのシード

Springを使用して簡単なワークフローを組み合わせる方法がわかったので、シードデータを使用したインスタンス化に焦点を当てましょう。ワークフローをシードする方法を理解するために、実際のProcessorインターフェースで公開されているメソッドを見てみましょう。

public interface Processor { public boolean supports(Activity activity); public void doActivities(); public void doActivities(Object seedData); public void setActivities(List activities); public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler); }

ほとんどの場合、ワークフロープロセスでは、キックオフのためにいくつかの初期刺激が必要です。プロセッサを起動するには、2つのオプションがあります。doActivities(Object seedData)方法または引数のない代替方法です。次のコードリストは、サンプルコードに含まれているもののdoAcvtivities()実装SequenceProcessorです。

 public void doActivities(Object seedData) { if (logger.isDebugEnabled()) logger.debug(getBeanName() + " processor is running.."); //retrieve injected by Spring List activities = getActivities(); //retrieve a new instance of the Workflow ProcessContext ProcessContext context = createContext(); if (seedData != null) context.setSeedData(seedData); for (Iterator it = activities.iterator(); it.hasNext();) { Activity activity = (Activity) it.next(); if (logger.isDebugEnabled()) logger.debug("running activity:" + activity.getBeanName() + " using arguments:" + context); try { context = activity.execute(context); } catch (Throwable th) { ErrorHandler errorHandler = activity.getErrorHandler(); if (errorHandler == null) { logger.info("no error handler for this action, run default error" + "handler and abort processing "); getDefaultErrorHandler().handleError(context, th); break; } else { logger.info("run error handler and continue"); errorHandler.handleError(context, th); } }