[WF 4 (12)] Workflow Extensions と 永続化、トラッキング

(2010/07 : RTM 版にあわせて修正)

環境 : Visual Studio 2010 Beta 2 (.NET Framework 4)

WF 4

  1. そのアイデアとベースクラスの変更
  2. コードいらずのワークフロー (入門)
  3. ワークフローのコードと XAML の内部
  4. フローチャート (FlowCharts)
  5. アクティビティ (基礎)
  6. アクティビティコンテキスト (context) と変数 (Variable) の意義
  7. アクティビティにおけるさまざまな実行管理
  8. アクティビティのデリゲート
  9. アクティビティの非同期実行
  10. アクティビティデザイナー
  11. ブックマーク
  12. Workflow Extensions と 永続化、トラッキング
  13. デザイナーリホスティングとカスタムアプリケーション

こんにちは。

もたもた書いていられないので、乱暴ですみませんが、これらのネタをまとめて記載したいと思います。(多少長くなると思いますが、ご辛抱ください。。。)

Workflow Extensions の基本

乱暴な言い方かもしれませんが、従来の WF をご存じの方には、Workflow Extensions は、ワークフローに登録するサービス (永続化サービス、データ交換サービス、など) と同じと言えばおわかり頂けるでしょう。従来の WF をご存じない方でもわかるように、以下に簡単に例示してみます。

下記の例では、IPrint という Workflow Extension 用のインタフェースを用意し、このインタフェースを ConsolePrint というクラスで実装して、この実装された Workflow Extension を WF のインスタンスに設定しています。カスタムの PrintActivity では、この挿入された Workflow Extension (ConsolePrint) を取り出して、Print メソッドを呼び出しています。

// カスタムの Extension の定義
public interface IPrint
{
    void Print(string val);
}

public class ConsolePrint : IPrint
{
    public void Print(string val)
    {
        Console.WriteLine("print : {0}", val);
    }
}

// アクティビティの定義
public class PrintActivity : CodeActivity
{
    public InArgument<string> PrintText { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        // Extension を取得して処理を実行 !
        string printText = PrintText.Get(context);
        IPrint printEx = context.GetExtension<IPrint>();
        printEx.Print(printText);
    }
}

// 動作確認
. . .

static void Main(string[] args)
{
    AutoResetEvent t = new AutoResetEvent(false);

    WorkflowApplication app = new WorkflowApplication(
        new PrintActivity { PrintText="Tsuyoshi" })
    {
        Completed = (e) =>
        {
            Console.WriteLine("Completed Called !");
            t.Set();
        }
    };

    // Extension の設定
    ConsolePrint printEx = new ConsolePrint();
    app.Extensions.Add(printEx);

    // インスタンスの実行
    app.Run();

    t.WaitOne();

    Console.ReadLine();
}

上記のサンプルコードで、例えば、IPrint を実装した別のクラス (例 : プリンターに出力するクラス、メッセージボックスを表示するクラス、など) を挿入した場合を想像してみてください。つまり、ワークフローインスタンスではなく、Main の処理の側で、実際の動きをいろいろと定義することが可能になるわけです。
Workflow Extensions を使用すると、ワークフローインスタンスの構成とは無関係に、こうしたホスト側 (上記の Main) から挿入された Extension にあわせたワークフローの制御がおこなえます。

補足 : 上記では独自な拡張 (Extension) を作成していますが、IWorkflowInstanceExtension インターフェイスを実装して拡張 (Extension) クラスを作成すると、拡張機能の中でワークフロー インスタンスにアクセスして処理をおこなうことができます。(このため、通常は、このインターフェイスを実装します。)
また、Metadata の AddDefaultExtensionProvider メソッド を使用すると、特定の型の Extension がランタイムに設定されていない場合に、その型の Extension を追加することができます。
WF の標準アクティビティでは、このようにして、内部でいくつかの Extension が追加されています。

永続化 (Persistence) とトラッキング (Tracking)

ワークフローでは、例えば、数日にまたがった承認処理など、一般に、長時間の実行がおこなわれることが多いため、実行中のインスタンスをいつもメモリー上にロードしておくというわけにはいかないでしょう。また、承認の経過を表示するようなアプリケーションでは、ワークフローが現在どこまで進んでいるかという追跡 (トラッキング) の仕組みも必要になります。WF では、こうした、永続化 (Persistence) やトラッキング (Tracking) の処理においても、上記の Workflow Extensions のメカニズムが内部で使用されています。

では、まずは、永続化の簡単なサンプル コードを作成して使い方を確認してみましょう。(その後で、Workflow Extension を活用した拡張方法について簡単に説明します。)

今回、Idle 時にメモリから退避 (Unload) させて永続化 (Persist) をおこなうサンプルを作成します。このため、前回作成した WaitInputActivity (下記) を使って実験してみましょう。

public class WaitInputActivity : NativeActivity
{
    protected override bool CanInduceIdle
    {
        get
        {
            // Idle になる可能性があることを明示 !
            return true;
        }
    }

    protected override void Execute(NativeActivityContext context)
    {
        Console.WriteLine("Bookmark Start.");
        context.CreateBookmark("Bookmark1-MyWorkflow",
            new BookmarkCallback(OnResume));
    }

    void OnResume(NativeActivityContext context,
        Bookmark bookmark, object value)
    {
        Console.WriteLine("OnResume Called ! : {0}",
            value.ToString());
    }
}

WF では、簡単に永続化を実装したい開発者のために、SQL Server 用の永続化の仕組みが既定で提供されています。このサンプルコードでは、この SQL Server 用の永続化ストアを使用します。

まず、永続化させるためのデータベースを SQL Server に作成 (create database 文) しておきましょう。つぎに、%systemroot%Microsoft.NETFrameworkv4.0.21006SQLja に、永続化用のスキーマ作成のスクリプトがあるので、この中の SqlWorkflowInstanceStoreSchema.sql, SqlWorkflowInstanceStoreLogic.sql を作成したデータベースに対して実行します。

sqlcmd -S .sqlexpress -d TestDB -E -i SqlWorkflowInstanceStoreSchema.sql
sqlcmd -S .sqlexpress -d TestDB -E -i SqlWorkflowInstanceStoreLogic.sql

注意 : 同じフォルダ内に、SqlPersistenceService_Schema.sql, SqlPersistenceService_Logic.sql がありますが、これは、旧 WF 3.5 のときの永続化サービス用のスクリプトがそのまま置かれています。また、SqlPersistenceProviderSchema.sql, SqlPersistenceProviderLogic.sql は、WCF で Durable サービスを実装する際に使用する SQL Server 用の永続化プロバイダーです。

なお、Tech Days 2010 ではデモでお見せする予定ですが、Windows Server AppFabric の Application Extension をインストールして IIS 管理マネージャから初期化をすると、上記のスキーマが Application Extension データベースに作成されます。

つぎに、コードを記述します。
[ワークフローコンソールアプリケーション] のプロジェクトを作成して、System.Runtime.DurableInstancing.dll、System.Activities.DurableInstancing.dll を参照追加します。
そして、上記の WaitInputActivity を使用した下記のコードを実装しましょう。

. . .
using System.Runtime.DurableInstancing;
using System.Activities.DurableInstancing;

static void Main(string[] args)
{
    AutoResetEvent t = new AutoResetEvent(false);

    InstanceStore ins = new SqlWorkflowInstanceStore(
        @"Data Source=.sqlexpress;Initial Catalog=TestDB;Integrated Security=True");
    InstanceView view = ins.Execute(ins.CreateInstanceHandle(),
        new CreateWorkflowOwnerCommand(),
        TimeSpan.FromSeconds(30));
    ins.DefaultInstanceOwner = view.InstanceOwner;

    WorkflowApplication app = new WorkflowApplication(new Sequence
        {
            Activities =
            {
                new WriteLine{ Text="1st activity !" },
                new WaitInputActivity(),
                new WriteLine{ Text="last activity !" }
            }
        })
        {
            InstanceStore = ins,
            PersistableIdle = (e) =>
            {
                Console.WriteLine("PersistableIdle Called !");
                return PersistableIdleAction.Unload;
            },
            Unloaded = (e) =>
            {
                Console.WriteLine("Unloaded Called !");
                t.Set();
            },
            Completed = (e) =>
            {
                Console.WriteLine("Completed Called !");
                t.Set();
            }
        };

    // start and unload
    app.Run();
    Console.WriteLine("Instance Id : {0}", app.Id.ToString());

    // load and continue
    //app.Load(new Guid("Some-Guid-Value"));
    //app.ResumeBookmark("Bookmark1-MyWorkflow", "Tsuyoshi");

    t.WaitOne();

    Console.ReadLine();
}

上記のコードでは、InstanceStore = ins の行によって、作成するワークフローインスタンス (WorkflowApplication) に SQL Server 用の InstanceStore (SqlWorkflowInstanceStore) を設定しています。

また、以下のコードの箇所により、(永続化可能な) アイドルイベントが発生した場合に、メモリーからインスタンスのアンロード (Unload) をおこなって、データベースへの永続化 (Persist) をおこなうように、ワークフローインスタンスに指示をしています。

PersistableIdle = (e) =>
{
Console.WriteLine(“PersistableIdle Called !”);
return PersistableIdleAction.Unload;
}

このため、このワークフロー インスタンスでは、最初の WriteLine が実行され、つぎの WaitInputActivity が実行されると、WaitInputActivity の中でブックマークが発生してアイドル状態になり、メモリからアンロードされて、データベースに永続化されます。(つまり、最後の WriteLine は実行されずに、データベース中に退避されます。)

実行結果は、以下になります。(実行した結果をそのまま貼り付けました。。。)

Instance Id : 483539c4-004e-4abc-98ce-0ebb26ddf451
1st activity !
Bookmark Start.
PersistableIdle Called !
Unloaded Called !

ちなみに、上記で作成した SQL Server データベース内の InstancesTable テーブルを SQL Server 上から確認すると、ちゃんとインスタンスが 1 レコード登録されているはずです。

つぎに、上記のコンソールに出力されたワークフロー インスタンスIDを使用して、再び、このワークフロー インスタンスを呼び戻してみましょう。(下記のソースコードで、ワークフローインスタンスIDの箇所は、皆さんの実行結果にあわせて変更しておいてください。)

static void Main(string[] args)
{
    AutoResetEvent t = new AutoResetEvent(false);

    InstanceStore ins = new SqlWorkflowInstanceStore(
        @"Data Source=.sqlexpress;Initial Catalog=TestDB;Integrated Security=True");
    InstanceView view = ins.Execute(ins.CreateInstanceHandle(),
        new CreateWorkflowOwnerCommand(),
        TimeSpan.FromSeconds(30));
    ins.DefaultInstanceOwner = view.InstanceOwner;

    WorkflowApplication app = new WorkflowApplication(new Sequence
        {
            Activities =
            {
                new WriteLine{ Text="1st activity !" },
                new WaitInputActivity(),
                new WriteLine{ Text="last activity !" }
            }
        })
        {
            InstanceStore = ins,
            PersistableIdle = (e) =>
            {
                Console.WriteLine("PersistableIdle Called !");
                return PersistableIdleAction.Unload;
            },
            Unloaded = (e) =>
            {
                Console.WriteLine("Unloaded Called !");
                t.Set();
            },
            Completed = (e) =>
            {
                Console.WriteLine("Completed Called !");
                t.Set();
            }
        };

    // start and unload
    //app.Run();
    //Console.WriteLine("Instance Id : {0}", app.Id.ToString());

    // load and continue
    app.Load(new Guid("483539c4-004e-4abc-98ce-0ebb26ddf451"));
    app.ResumeBookmark("Bookmark1-MyWorkflow", "Tsuyoshi");

    t.WaitOne();

    Console.ReadLine();
}

実行結果は、下記の通りになります。(ワークフローインスタンスが、再びメモリ中にロードされ、続きから処理がおこなわれているのがわかります。)

OnResume Called ! : Tsuyoshi
last activity !
Completed Called !
Unloaded Called !

ちなみに、この場合、ワークフローが完了すると、上記の InstancesTable テーブルからも該当のレコードは削除されます。

Note : このレコードが削除されるというのは既定の動作ですが、上記のコードで作成した SqlWorkflowInstanceStore のオブジェクト (ins) の InstanceCompletionAction プロパティの値を InstanceCompletionAction.DeleteNothing にすると、完了後もインスタンスの情報がデータベース内に残せます。このように、プロパティを設定して、きめ細かな制御が可能です。

さて、この永続化の拡張方法 (Customization) について考えてみましょう。

WF 4 では、永続化処理そのものを担当する InstanceStore クラス (InstanceStore を継承したクラス) と、Workflow Extension の 1 つである PersistenceParticipant クラス (PersistenceParticipant を継承したクラス) によって、永続化の処理をカスタマイズできます。上記では SQL Server に永続化をおこなう SqlWorkflowInstanceStore クラスを使用しましたが、例えば、独自の InstanceStore クラスを作成して、ファイルに保存をおこなう処理などを実装できます。また、PersistenceParticipant クラスを継承して、このクラスを Workflow Extension に追加することで、永続化をおこなうカスタムなデータを追加できます。永続化のパイプラインは、データを永続化 (Unload) させる際に、Workflow Extension のすべての PersistenceParticipant クラスを取り出し、それぞれの PersistenceParticipant について、CollectValues メソッドを呼び出します。このため、 カスタムな PersistenceParticipant クラスを作成して、CollectValues メソッドを override することで、永続化の際のデータ辞書を独自に追加できます。また逆に、永続化ストアから、メモリ中の Workflow Extension オブジェクトにデータを戻す際 (Load) は、PublishValues メソッドが呼び出されます。(ロードの処理は、この PublishValues メソッドを override します。)
この WF 4 の永続化のパイプライン処理 (各クラスを使用した処理の流れ) については、以下に記載されていますので参考にしてください。

http://msdn.microsoft.com/en-us/library/ee473464(VS.100).aspx

また、今回は永続化の説明だけでしたが、トラッキング (Tracking) も簡単に実現可能で、トラッキングは、TrackingParticipant から継承されたクラス (override メソッドである Track メソッドを実装すれば OK です) を作成して Workflow Extension に追加することで、独自にカスタマイズすることが可能です。(例えば、コンソール上にトラッキングの結果を出力する独自の Extension などを作成できます。) なお、WF 4 には、EtwTrackingParticipant という ETW を使ったトラッキングのクラスが既に用意されており、Windows Server AppFabric ではこれが使用されています。また、トラックする内容をカスタマイズする場合は、TrackingProfile と呼ばれるクラスを使用し、上述した TrackingParticipant の TrackingProfile プロパティにこのプロファイルを設定すれば OK です。

なお、ここでは、ワークフロー サービス (WCF/WF 連携サービス) については説明しませんが、ワークフロー サービスではこうした設定 (上述した InstanceCompletionAction などの細かな設定も含め) を構成ファイル (.config) に定義することが可能です。

ということで、あとはリホスティングの話 (WF 4 では、非常に簡単になっています !) を記載して、最終回にしたいと思います。。。(大急ぎな説明で、すみません。。。)

 

 

One thought on “[WF 4 (12)] Workflow Extensions と 永続化、トラッキング

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s