WF 4 の補正処理 (トランザクション) と CompensationExtension


環境 :
Visual Studio 2010 Service Pack 1 (SP1 - 日本語)
.NET Framework 4 Platform Update 1 - Design-time Update for Visual Studio 2010 SP1 (日本語)

.NET 4 Platform Update 1 の WF 新機能

こんにちは。すみません、こちらの連載を、すっかり放置していました。。。

今回は、.NET Framework 4 Platform Update 1 の WF (Windows Workflow Foundation) で使用可能になった CompensationExtension について説明します。
しかし、その前に、そもそも WF 4 の補正処理 (Compensation) を知らない方のために、まずは、その概要と使い方をちゃんと説明しておきたいと思います。(というか、ほとんどその話で終わりです . . .)

なお、 WF 3.5 にもこの補正 (Compensation) の考え方はありましたが、一部、WF 4 で概念が変わっている箇所もありますので、WF 3.5 までの補正処理をご存じな方も、是非 復習として理解しておいてください。、

補足 : WF 3.5 の頃の補正処理については、以前 投稿した「WF 標準アクティビティの歩き方 (Base Activity Library)」を参考にしてください。

補正処理 (Compensation) とは ?

ワークフローでは、承認処理のように、何日にもまたがって実行される処理が多数考えられます。このようなケースでトランザクション処理をおこないたい場合、データベースなどで一般的に使われるアトミック トランザクション (Atomic Transaction) や分散トランザクションを使用するのは現実的ではありません。数日間もワークフローをメモリ中にロードしておくのは、好ましくないでしょう。

そこで、こうした long-running なワークフローで活躍するのが、この補正処理です。補正処理は、ざっくりと説明すると、ロールバック用の処理を開発者自らが定義し、例外 (エラー) 発生時に、この定義された処理を呼び出すというものです。

想像していただくとわかりますが、補正処理では、複数のブロックが繰り返し処理されたり、ブロックどうしがネストするような複雑な関係になると、すべての処理をワークフローで定義するのは困難です。WF の補正処理では、WF 3.5 の頃から、こうした複雑な補正処理を扱えるような仕組み (メカニズム) が提供されています。

なお、WF 3.5 では、補正処理を扱う場合に、必ず、永続化サービスと一緒に使う必要がありましたが、WF 4 以降では、永続化 (Persistence) を設定しなくても補正処理が可能です。(無論、この場合、常にメモリ中にインスタンスが存在しているので、アトミック トランザクションなどで代用できる場合もありますが。)

WF 4 の補正処理のアクティビティ (超入門)

では、まず、使用するアクティビティと、それぞれの使い方を簡単に説明しましょう。

まず、WF 4 のトランザクション関連のアクティビティには、以下 (下図) があります。これらのうち、TransactionScope アクティビティ以外のものが、すべて補正処理に関連したアクティビティです。

まず、最も基本的な (必須の) アクティビティが、CompensableActivity アクティビティ (下図) になります。

CompensableActivity アクティビティには、上図の通り、処理本体の Body と、CompensationHandler、ConfirmationHandler、CancellationHandler の 3 つのハンドラーがあります。(これら 3 つのハンドラーをすべて設定する必要はありません。)

まず、CompensableActivity アクティビティでは、上図の Body に、このブロックにおける処理本体を入れます。

そして、Body が完了すると、この処理が終了したとみなされて、以降の補正処理の対象となります。Body がすべて終了し、その後、どこかで Compensate アクティビティが呼び出されると、上図の CompensationHandler が呼び出されます。
例えば、Body の中でアイテムの登録処理をおこない、CompensationHandler でこのアイテムの削除の処理を入れておきます。Body が終了してこのブロックを出た後、承認処理で却下された場合に Compensate アクティビティを呼ぶようにしておくと、却下された際にアイテムが削除されます。

もし、例外 (エラー) 発生時にこの CompensationHandler を呼びたいなら、Try - Catch (TryCatch アクティビティなど) をおこなって、Catch ブロックの中で Compensate アクティビティを呼び出せば CompensationHandler が呼ばれます。

つぎに、CancellationHandler (上図) は、Body の処理が途中でキャンセルされた場合に呼ばれる処理です。例えば、ある Body の処理の途中で例外 (エラー) を Throw し、TryCatch アクティビティによって外で終了処理がおこなわれる場合を想像してください。この場合、Body の処理が途中でキャンセルされるため、この CancellationHandler が呼び出されます。

ConfirmationHandler は、データベースのコミットのように、処理が確定した段階で呼び出させます。
例えば、A と B の 2 つの CompensableActivity アクティビティを逐次 (Sequential) に実行して終了する簡単なワークフローを想像してみましょう。この場合、B のアクティビティが終了してワークフロー全体が完了した段階で、双方 (A, B) の ConfirmationHandler が呼び出されます。(Confirm アクティビティを明示的に呼び出さなくても、ワークフローの完了時に自動的に呼び出されます。)
データベースにおけるコミット処理のように、処理を途中で確定させたい場合には、Confirm アクティビティを使います。例えば、A と B の 2 つの CompensableActivity アクティビティを逐次 (Sequential) に実行する場合、A の終了後に Confirm アクティビティを呼び出すと、B の実行の前に、いったん A が完了 (確定) され、A の ConfirmationHandler が呼び出されます。

なお、補正処理 (Compensation) を扱わず、キャンセル処理 (Cancellation) だけを扱いたい場合は、CancellationScope アクティビティ (下図) が使用できます。(このアクティビティの使い方は、上記の CancellationHandler と同様です。)

参考 : TransactionScope アクティビティは、ここで説明している「補正処理」ではなく、アトミック トランザクションや分散トランザクション (例 : データベースに対するロールバック処理、など) を処理するためのブロックです。.NET の TransactionScope クラスと同じ振る舞いをします。

補正処理のブロック設計

ここから、少し難しくなります。
WF 4 では、ブロックどうしの細かな制御ができるように設計されているため、CompensableActivity アクティビティの各ブロックの関係を細かく設計しておく必要があります。

まず、A と B の 2 つの CompensableActivity アクティビティを逐次 (Sequential) に実行し、終了後に Compensate アクティビティを呼び出す場合を考えてみましょう。この場合、期待される動きとして、B と A の CompensationHandler が呼ばれると思うかもしれませんが、実は、WF 4 では、そのようには動作しません。(WF 3.5 の頃は、そのように動作していました。)

WF 4 では、Compensate アクティビティが呼ばれると、対応する直前の CompensableActivity アクティビティの CompensationHandler のみが呼び出されます。このため、上述のように 2 つの CompensableActivity アクティビティに対する補正処理をおこなう場合は、下図のように、CompensableActivity アクティビティの CompensationHandler の中で、再度 Compensate アクティビティを実行して、Body 内部にネストされた CompensableActivity アクティビティの CompensationHandler を呼び出すようにします。(下図の場合、CompensableActivity 3、CompensableActivity 2 の CompensationHandler が呼び出されます。)

上図で、各ハンドラーの呼ばれる順番 (実行結果) は以下の通りです。

  1. CompensableActivity 2 の Body
  2. CompensableActivity 3 の Body
  3. CompensableActivity 1 の CompensationHandler
  4. CompensableActivity 3 の CompensationHandler
  5. CompensableActivity 2 の CompensationHandler

補足 : 上図のトークン (token) は、CompensableActivity アクティビティと Compensate アクティビティの対応関係を設定するためのものです。下図のように、ワークフロー変数 (Variables) として System.Activities.Statements.CompensationToken クラスの変数を作成し (複数可能)、対応するアクティビティどうしで設定をおこないます。
ネストされた CompensationHandler を処理するには、上図のように、ネストされたアクティビティでトークン (token) を Null に設定します。

上図で、CompensableActivity 1 の CompensationHandler で何かの処理を実行してから Compensate アクティビティを呼び出すことで、ネストされた CompensableActivity (CompensableActivity 2、CompensableActivity 3) の CompensableHandler もあわせて処理することができます。(全体の補正処理と、部分的な補正処理を、順番に処理できます。)

なお、CompensationHandler に処理が含まれていない場合、ネストされた CompensableActivity アクティビティの CompensationHandler に処理が遷移します。このため、下図のように処理しても、上記と同じ実行結果になります。

賢い ! (Cool !)

上記で、CompensableActivity 2 と CompensableActivity 3 の CompensationHandler が呼ばれる順番に注目してください。
Body では、CompensableActivity 2、CompensableActivity 3 の順番で処理がおこなわれているため、逆に、CompensableActivity 3、CompensableActivity 2 の順番で CompensationHandler が呼ばれているのがわかります。WF 4 では、WF 3.5 同様、あたかも、インスタンスの実行履歴が参照されているかのように、適切な順番と回数で補正処理が呼び出されます。

例えば、下図の通り処理された場合を想像してみてください。

この場合、最後の Compensate アクティビティによって、CompensableActivity 1 の補正処理 (CompensationHandler) のみが呼び出されます。

つぎの場合はどうでしょう ? (While の中で、CompensableActivity が 3 回呼ばれています。)

この場合は、最後の Compensate アクティビティによって、CompensableActivity 2 の補正処理 (CompensationHandler) が 3 度呼び出されます。また、CompensableActivity 2 のスコープの変数 (Variables) がある場合、それぞれの CompensationHandler では、対応する Body が実行されたときの変数の値が維持されます。(例えば、Id 情報などを保持して、対応する Id ごとに補正処理をおこなうことなどができます。)

公開された CompensationExtension !

WF 4 では、CompensationToken に関する情報や、ConfirmationHandler / CompensattionHandler などの各ハンドラー (Activity) のブックマーク (Bookmark) の情報などは、すべて、内部で作成される CompensationExtension という Workflow Extension の中に格納されていて、上記の Confirm アクティビティ、Compensate アクティビティなども、この情報を使って補正処理をおこなっています。(ブックマークが使用されているため、上記の While の例で説明したように、そのコンテキストにおける変数の情報なども、ちゃんと保持されています。)

補足 : ここでは、Workflow Extension や、永続化のメカニズム (下記の PersistenceParticipant など) についての説明は省略します。詳細は、以前投稿した「WF 4 : Workflow Extension と 永続化、トラッキング」を参照してください。
なお、この CompensationExtension は、CompensableActivity が内部で作成 (追加) しています。(ただし、既に、CompensationExtension がワークフローに設定されている場合は、追加されません。)

また、この CompensationExtension は PersistenceParticipant を継承したクラスです。このため、永続化と共に使用しても、上述の補正処理 (Compensation) に関する情報は正しく永続化ストア (データベースなど) に保存され、必要に応じてメモリへのロードがおこなわれます。

そして、.NET Framework 4 まで、この CompensationExtension は、internal、かつ sealed のクラスでしたが、.NET Framework 4 Platform Update 1 では、このクラスは開発者に公開されていて、このクラス (CompensationExtension) を継承することも可能です。(これが、Platform Update 1 の進化のポイントです !)

おまけ . . .

しかし、ここまで書いていて何ですが、これ (CompensationExtension)、どう使うんでしょうね ? CompensationExtension のメンバーのほとんどは private か internal です。しかし、CollectValues、PublishValues が override 可能ですので、補正処理 (Compensation) そのものに独自のカスタマイズと永続化をおこないたい場合などに使用できるかもしれませんね . . . (今後、.NET Endpoint チーム ブログなどで、こうしたサンプルを教えてくれると嬉しいですね . . .)

まあ、いずれにせよ、こうした複雑な補正処理を扱えるという点だけでも、WF を使用する価値があることがお分かり頂けたでしょう。

 

Comments (0)

Skip to main content