Durable Functions for Node.js を試してみた

Build2018でいよいよGAとなったDurable Functionsですが、同時にこっそりNode.js版がパブリックプレビューとなっていました。

個人的にFunctionsの実装はNode.jsがお気に入りなので、これはちょっと嬉しい発表でした。早速自分のMac + VSCode環境で動くかどうかを試してみたのでここで紹介します。

Azure Functions 開発環境の準備

これはDurable Functionsに限った話ではないですが、Mac + VSCodeでAzure Functionsを動かすためには事前に以下のような環境を構築しておく必要があります。

  • Node.js
  • .NET Core SDK
  • Azure Functions Core Tools

環境構築手順をここに書くと長くなるので、Qiitaに別記事を書いておきました。

Durable Functionsの作成

Durable Functions for Node.jsの公式リポジトリ に導入から実行手順まで一通りの説明はありますが、若干微妙な箇所があったのと一部説明が不足している箇所もあったので、そのあたりも含めて手順をまとめてみました。なお、Durable Functions自体の解説については、公式docsのドキュメントが非常に充実しているので、そちらを参照して下さい。

拡張機能の導入

VSCodeの環境で Durable Functions を作成するには、プロジェクトに Azure Functions Durable Extension をインストールする必要があります。なお、このコマンドではバージョン指定が必須のため、リポジトリのリリース一覧 でバージョンを確認しておきます。公式ドキュメントでは、1.3.3をを指定していますが、1.4.1でも問題はありませんでした。

func extensions install -p Microsoft.Azure.WebJobs.Extensions.DurableTask -v 1.4.1

Extensionに加えて、Node.js用のDurable Functionsパッケージも追加する必要がありあす。これはnpmで導入できるので、事前にnpm initを実行してからパッケージを導入します。

# package.jsonの生成
npm init
# パッケージのインストール
npm install durable-functions

比較的新しいnpmでは--save-devがデフォルトになっているので付けていません。また、lockファイルで依存関係の管理をするのが主流になっていますので、生成(更新)されるpackage-lock.jsonはGit管理に含めるようにしました。

ストレージエミュレータの利用について(Issueあり)

ローカルでのFunctions実行はできるだけストレージエミュレータを使うべきです。 さきほどのQiita記事にも書いた通り、Mac環境では Azurite というクロスプラットフォームのエミュレータが使えるのですが、Durable FunctionsではAzuriteを使った環境ではうまく動きませんでした。Durable Functionsの開発者である @cgillum にも相談しましたが、多分Azurite側の何らかの制約があってDurableが内部で使うQueueかTableが機能してないとのこと。一応Issueをあげておきました。

とりあえず現時点では、Azure上のストレージアカウントを利用するようにlocal.setting.jsonを設定して回避しました。

Orchestratorクライアントの実装

公式リポジトリのサンプルでは、なぜかOrchestratorクライアントをcsxで作成していて残念な気持ちになったので、多少試行錯誤してJavaScriptで書き直してみました。

Orchestratorクライアントは、function.jsonのアウトプット側のtypeをorchestrationClientにしておくと、Orchestratorを呼ぶことができます。

{
"disabled": false,
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"name": "starter",
"type": "orchestrationClient",
"direction": "out"
}
]
}

index.jsの方ではOrchestratorのインスタンスIDを生成するため、uuidパッケージを利用しています。事前にnpm i uuidを実行しておきます。

const uuid = require('uuid');

module.exports = function (context, req) {
const id = uuid.v4(); // オーケストレータのインスタンスIDを生成
context.bindings.starter = [{
FunctionName: req.body.functionName, // 実行するオーケストレータ名を指定
Input: req.input,
InstanceId: id
}];
context.done(null, id);
};

OrchestratorとActivityの実装

OrchestratorとActivityは公式サンプルのFunction Chainingの例をほぼそのままにしています。非同期処理に何故かgeneratorを使っていたので、aync/awaitへの書きかえも試みましたが、うまくActivityが呼ばれなかったのでもう少し調べたいと思います。

Orchestrator Function

const df = require("durable-functions");

module.exports = df(function*(context){
context.log("Starting chain sample");
const output = [];
output.push(yield context.df.callActivityAsync("say-hello", "Tokyo"));
output.push(yield context.df.callActivityAsync("say-hello", "Seattle"));
output.push(yield context.df.callActivityAsync("say-hello", "London"));

return output;
});

Activity Function

module.exports = function(context) {
context.done(null, `Hello ${context.bindings.name}!`);
};

実行結果の確認

実行結果はlocal.settings.jsonで設定されたストレージアカウントをAzure Storage Explorerで確認します。正常に実行されれば、テーブルストレージのDurableFunctionsHubHistoryにだいたい以下のような出力がされているはずです。

Azure Storage Explorer

OrchestrationStatusCompletedになっているので、Durable Functionsが正常終了していることがわかります。

Durable FunctionsのNode.js版はまだ開発が始まったばかりなので、対応している機能はまだ限られていますが、公式Docsの各種サンプルコードが既にC#とJavaScriptの両方が記載されるようになっていることからもその本気度がうかがえます。ちなみに現状で対応している機能や制限事項は以下にまとまっていました。今後に期待したいと思います。