2009年7月20日月曜日

Long-running WFサービス の永続化について

もう必要なくなったが、WFの調査を少しだけ続けてみた。 今回は、永続化サービスを調べてた。 参考記事は↓ ワークフロー サービスと永続性サービスの作成(msdn) Hands-on Labs for WindowsR Workflow Foundation in C# and VB.NET 1. 調べたこと 人手が介在するワークフローを想定する場合、進行中のワークフローが人手作業の結果の受信を待っている状態が、長時間続くことが当然考えられる。その間に、ワークフローをホストしてる環境が再起動されたり、あるいはダウンしたりする事も想定されるが、ほとんどの業務では、進行中のフローが無に帰してしまうのはNGであるはず。したがってワークフローランタイムやホスト環境のスコープを超える永続化の仕組みが必要となるが、.NETではこれをどう解決してるだろうか。これを調査して実験により確認してみたので、以下に記す。 2.ワークフロー 想定するワークフローは次のようなものを用意してみた。
図1
図中、二つのCodeアクティビティには以下のように ExecuteCode ハンドラを対応付ける。
private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
returnValue = this.WorkflowInstanceId.ToString();
}
private void codeActivity2_ExecuteCode(object sender, EventArgs e)
{
returnValue = "succeeded";
}
図のフロー定義とコードをみて分かるように、このハンドラは、最初の受信でワークフローインスタンスID(以下 instanceId )を返し、二度目の受信では"succeeded"を返す。 3. クライアント 上のフローとの通信により、まずクライアントは最初の呼び出しで instanceId を取得し、次に固定文字列"succeeded"が受信できる事を期待して2度目の呼び出しを行う。これはWindows フォームで実装してみた。
図2 図3 図4
図2は初期状態である。ここで[step1]ボタンを押すと、最初のワークフロー呼出を行い instanceId を取得し、次の図3のような状態になる。さらに[step2]ボタンを押すと、成功すれば図3のように"succeeded"が表示されるが、失敗するば図4のように例外メッセージが表示される。 これを以下のようなコードで実装し、適当にハンドラとして関連付けた。
private void btnStep1_Click(object sender, EventArgs e)
{
Workflow1Client proxy = new Workflow1Client();

label1.Text = proxy.GetData(0);
btnStep1.Enabled = false;
btnStep2.Enabled = true;
label2.Text = "";

proxy.Close();
}

private void btnStep2_Click(object sender, EventArgs e)
{
Workflow1Client proxy = new Workflow1Client();
IContextManager contextManager = proxy.InnerChannel.GetProperty<IContextManager>();

Dictionary context = new Dictionary<string, string>();
context.Add("instanceId", label1.Text);
contextManager.SetContext(context);

try
{
   label2.Text = proxy.GetData(0);
   btnStep1.Enabled = true;
   btnStep2.Enabled = false;
   label1.Text = "";
}
catch (Exception excp)
{
   label2.Text = excp.Message;
}
proxy.Close();
}
4. 何もしないと永続化されない事を確認 まずはワークフローサービスが起動しっぱなし永続化を考慮しない状態で、クライアントとの協調が成立する事を確認する。
  1. [F5]押下でWCFサービスを起動(フロー側のプロジェクトがスタートアップの前提)
  2. クライアントフォームを起動
  3. [step1]を押下して instanceId が取得できることを確認
  4. [step2]を押下して succeeded が返される事を確認
  5. 念のため3,4を数回繰り返して正しく動作する事を確認
次に、ワークフローを途中まで進めてから一度WCF サービスを落してみると、永続化されていないために、当然、再起動しても落ちた時点からのフロー再開ができない事を確認する。
  1. [F5]押下でWCFサービスを起動(フロー側のプロジェクトがスタートアップの前提)
  2. クライアントを起動 (自分の場合、Debugフォルダからexeを直接叩いた。)
  3. [step1]を押下して instanceId が取得できることを確認
  4. この状態で、WCF テストクライアントを終了して、再度[F5]で起動
  5. クライアントフォームから[step2]を押下すると、"succeeded"の代わりにエラーメッセージが表示される事を確認
5.設定ファイルに追記 これを回避するために以下のようなコードを App.config 中、<behavior>のなか、<serviceCredentials>の下に追記した。
<workflowRuntime cachedInstanceExpiration="00:01:00"
validateOnCreate="false">
<services>
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
System.Workflow.Runtime,
Version=3.0.00000.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
ConnectionString="Initial Catalog=WorkflowStore; Data Source=localhost\SQLExpress; Integrated Security=SSPI;"
UnloadOnIdle="true"/>
</services>
</workflowRuntime>
以下、注記数点
  • ConnectionString と UnloadOnIdle について、VisualStudioがそんな属性は知らないと文句を言ってくるが無視する。気を使って<commonParameters>などに記述を移動すると、思った通りに動かなくなる。これに関連してmsdnのここで"SQL 追跡サービス"に関するメモという形で注意書きがあったが、永続化サービスでも同様らしい。
  • ConnectionString の実際の接続文字列については、適宜変更が必要。今回の例では、上記参考資料の『Hands-on Labs』の演習3で作成した、永続化サービス用データベースを流用している(Hands-on Labsは日本語版もあるがやや古い。最新の英語版は.NET 3.5対応)。
  • UnloadOnIdle="false"とすると、cachedInstanceExpirationで指定した時間が経過してから初めてDBに永続化されていた。したがってこの時間が経過する前にWCFテストクライアントを落とすと、ワークフローの現在状態が失われる。trueにすると、最初のメッセージ送信後、即座にDBに期待通りのレコードが生成されていることが確認できた。
6.永続化される事を確認 再び、先の実験と同じことをやってみる
  1. [F5]押下でWCFサービスを起動
  2. クライアントフォームを起動 (自分の場合、Debugフォルダからexeを直接叩いた。)
  3. [step1]を押下して instanceId が取得できることを確認
  4. この状態で、WCF テストクライアントを終了して、再度[F5]で起動
  5. クライアントフォームから[step2]を押下すると、今度はエラーメッセージではなく"succeeded"が表示される事を確認
以上で、永続化が確認できた。ちなみに手順3でinstanceId を取得した直後に、ConnectionStringで指定したDBカタログに作成されている WorkflowInstance テーブルを見てみると、現在クライアントフォームに表示されているのと同じ instanceId を持つレコードが生成されていることが確認できる。(VisualStudioのサーバエクスプローラで見られる。) 7.まとめ
  • 設定ファイルにサービスを記述するだけで機能を追加できるというのは、Java 業界でSpringの普及を契機に発展したIoCコンテナを取り入れたという事か。逆にJava業界では、.net のattributeを取り入れたJava1.5以降、外部のXML設定ファイルは避ける傾向にある気がするが・・・
  • Hands-On Labでは他にトラッキングとスケジューラが演習3で、カスタムのローカルサービスが演習5で紹介されている。

0 件のコメント:

コメントを投稿