Azure Durable FunctionsのLightweight Actorを試してみたので記載していきます。
試した内容はDurable Fuctionsの公式にサイトにあるSamplesとWalkthrouthsの内容です。
Azure Durable Functionsのローカル実行環境を構築していない人は下記を参照してください。
本記事はローカル実行環境が構築済みであることを前提とします。

その他のDurable Functionsのパターンについては下記を参照してください。


※本記事は2017年7月29日時点の情報となります。
※現時点ではWindosOSでのみ対応しているのでWindows10で実施した内容となります。
Function Lightweight Actor 基本編

Lightweight Actorは状態を格納し他の関数から長期的に参照することのできるオーケストレータ関数です。Service fabricのActorに似ているといわれています。
今回試したサンプルソースではカウンタシナリオでサーバ上の値を増減させます。ステートレスな場合は状態をサーバ側で保持できないので値の増減はできませんがステートフルな場合はサーバ側で値を保持し続けるので増減することが可能です。
カウンタのように増減するような操作はatomic(保護されている状態)である必要があります。なぜなら操作によって互いに上書きする競合状態が発生してしまうため並行性を管理する必要性があるためです。
オーケストレーションインスタンスは常にシングルスレッドなので、この種の動きは簡単に実装できます。それではサンプルソースを試してみましょう。サンプルソースにあるCounter.csがLightweight Actorのサンプルになります。
public static async Task<int> Run(DurableOrchestrationContext counterContext,TraceWriter log)
{
int counterState = counterContext.GetInput<int>();
log.Info($"Current counter state is {counterState}. Waiting for next operation.");
string operation = await counterContext.WaitForExternalEvent<string>("operation");
log.Info($"Received '{operation}' operation.");
operation = operation?.ToLowerInvariant();
if (operation == "incr")
{
counterState++;
}
else if (operation == "decr")
{
counterState--;
}
if (operation != "end")
{
counterContext.ContinueAsNew(counterState);
}
return counterState;
}
例によってVSでサンプルを実行しPOSTMANを起動して下記のURLをPOSTでたたきます。

今回はstatusQueryGetUriとSendEventPostUriの二つを利用します。statusQueryGetUriはいつもどおり状態の確認を行います。SendEventPostUriは指定した外部イベントを実行するためのAPIです。
まずはstatusQueryGetUriを実行しましょう。

処理の状態は実行中(Running)となっておりinputとoutputの項目がnullとなっています。増減のリクエストを送るとinputの値が変わっていくので試してみましょう。
増減のリクエスト送る場合はSendEventPostUriのURLを利用します。
まずURL中にある{eventName}をoperationに変更します。ソース中にあるWaitForExternalEventメソッドに指定のあるイベント名と同じにする必要があります。
string operation = await counterContext.WaitForExternalEvent<string>("operation");
次にリクエストの作成です。JSON形式でPOSTするためHeaderにContent-Typeとapplication/jsonを追加しましょう。

次に操作にインクリメント”incr”するのかデクリメント”decr”するのかBodyに記載します。

各種設定が完了したらSendして見ましょう。とりあえずここでは”incr”で3回実行します。
問題なくリクエストが処理されていればstatusQueryGetUriで状態を確認するとinputの部分に増減した値が表示されているはずです。

inputが3になっていると思います。次にbodyを”decr”にして1回実行してみましょう。
inputの値が1減って2になっていると思います。

最後にbodyに”end”を指定して実行してみましょう。

状態が完了となりoutputに最終的な値が入力されていると思います。これ以降は”incr”または”decr”を指定してPOSTを実行しても反映されません。
ソース上で肝になる部分はContinueAsNewで値を保持し続ける部分です。counterContext.GetInput<int>();で保持している値を参照することができるので、値を取得し増減させてContinueAsNewで保存するという流れになります。WaitForExternalEventで指定のイベント名が飛んできたら処理が発火します。イベント名が一致しない場合はWaitForExternalEvent以降の処理は継続されません。
サンプルソースを実行することでステートフルに値を処理できていることが確認できたと思います。
Function Lightweight Actor 応用編
サンプルソースを変更してカウンタではなく四則演算できるようにカスタマイズしてみましょう。
ようは計算機みたいな処理になります。
public static class Counter
{
[FunctionName("E3_Counter")]
public static async Task<double> Run(
[OrchestrationTrigger] DurableOrchestrationContext counterContext,
TraceWriter log)
{
double counterState = counterContext.GetInput<double>();
log.Info($"Current counter state is {counterState}. Waiting for next operation.");
CalculationInfo operation = await counterContext.WaitForExternalEvent<CalculationInfo>("calculation");
log.Info($"Received '{operation.CalcType}':'{operation.CalcValue}' Calculation.");
if (operation.CalcType == "+")
{
counterState += operation.CalcValue;
}
else if (operation.CalcType == "-")
{
counterState -= operation.CalcValue;
}
else if (operation.CalcType == "*")
{
counterState *= operation.CalcValue;
}
else if (operation.CalcType == "/")
{
counterState /= operation.CalcValue;
}
if (operation.CalcType != "end")
{
counterContext.ContinueAsNew(counterState);
}
return counterState;
}
}
public class CalculationInfo
{
public string CalcType { get; set; }
public double CalcValue { get; set; }
}
サンプルソースではString型でしたが今回はJSONで演算の種類と値を渡すようにするためCalculationInfoというクラスを作成してジェネリックに指定しています。EventNameも”operation”のままでは味気ないので”calculation”に変更しました。
ソースを修正したら実行してみましょう。下記のオーケストレータ関数を実行します。
計算のリクエスト送るにはSendEventPostUriのURLを利用します。まずURL中にある{eventName}をcalculationに変更しHeaderにContent-Typeとapplication/jsonの指定をしましょう。次にBodyのrawに下記のようにCalcTypeとCalcValueを指定します。ここでは1000を足す指定をしています。設定が完了したらPOSTでSendしてみましょう。

statusQueryGetUriで状態を確認します。inputに1000が表示されると思います。

次に5で割るを指定して実行してみましょう。

statusQueryGetUriで状態を確認します。inputに200が表示されると思います。

次にcを指定して実行しましょう。cはclearを意味します。処理上は0を代入しているだけです。

statusQueryGetUriで状態を確認します。inputに0が表示されると思います。

最後にendを指定して実行します。

statusQueryGetUriで状態を確認します。ステータスがCompletedになっていてinputとoutputが0になっていると思います。

サーバ側でステートフルに値を保持しつつ処理を実行できる事を確認できるたと思います。
使いどころは結構あると思うので重宝すると思います。
コメント