窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

Durable Functions を JavaScript + Azure Functions v3 環境で使う時にハマる点について(2020年7月現在の話)

タイトル通りです。

最近ちょっとしたプロダクトを作る時にDurable Functionsを採用したのですが、Azure Functions v3環境下において、 Azure Functions Core Tools@3 で生成されるTemplateでは期待通りの動作をしませんでした。 時間が経てば解決しそうな事象ではありますが、それまでに使う場合に辛いことにならないようにメモとして残しておきます。

ちなみに作ったプロダクトがこちら

github.com

環境

  • Azure Functions Core Tools 3.0.2630
  • Azure Functions Runtime ~3
  • durable-functions 1.4.3 (JSのPackage)

entityTrigger が動作しない

原因

JavaScriptのDurableFunctions packageで提供されているentityメソッドを発火するためのentityTriggerですが、こちらが実行できません。 原因としては func init を実行した際に出力されるhost.jsonの中身のextensionBundle の値が古いために、バンドルされる Microsoft.Azure.WebJobs.Extensions.DurableTask のバージョンが古すぎて entityTrigger のbindingがサポートされていないためです。

$ func --version
3.0.2630

な環境で作られる host.json の中身がこちらになります。

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

このバージョンレンジの中での現時点での最新は

azure-functions-extension-bundles/extensions.json at bd7c5b8adfb740dd8aaa1e60ec9f78b3c3c739cb · Azure/azure-functions-extension-bundles · GitHub

こちらで確認できるのですが、ここにバンドルされている Microsoft.Azure.WebJobs.Extensions.DurableTask のバージョンが 1.8.6 となっています。 entityのTriggerは2.0.0以降でのサポートとなっているため、利用が出来ない、というわけです。

解決策

host.jsonから extensionBundle の記述を削除し、必要なExtensionを自分で追加していくのが一番安心かと思います。 私はextensionBundleの記述を削除した後、

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

で最新のDurableTaskを導入し動作が確認できました。 この場合 .NET Coreがローカルに入っていないとextensionのビルドが出来ないため、インストールしておくのが良いでしょう。

ちなみにextensionBundleの記述がある状態で最新のDurableTaskをインストールしようとすると

No action performed. Extension bundle is configured in HERE_IS_AZURE_FUNCTIONS_FILE_PATH/host.json.

みたいな感じでインストールが出来ません。

continueAsNew でループを行えない

docs.microsoft.com

whileループなどを用いずに無限ループのようなものを作るパターンで利用するメソッドです。 2020年7月28日現在のドキュメントに記載されている

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

module.exports = df.orchestrator(function*(context) {
    yield context.df.callActivity("DoCleanup");

    // sleep for one hour between cleanups
    const nextCleanup = moment.utc(context.df.currentUtcDateTime).add(1, "h");
    yield context.df.createTimer(nextCleanup.toDate());

    context.df.continueAsNew(undefined);
});

だと2度しかこのOrchestratorが実行されません。

解決策

すでにIssueとして報告されています。

github.com

context.df.continueAsNew()yieldreturnなしで実行すると起こってしまうようです。

例えば

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

module.exports = df.orchestrator(function*(context) {
    yield context.df.callActivity("DoCleanup");

    // sleep for one hour between cleanups
    const nextCleanup = moment.utc(context.df.currentUtcDateTime).add(1, "h");
    yield context.df.createTimer(nextCleanup.toDate());

    yield context.df.continueAsNew(undefined);
    return '';
});

こんな感じでorchestrator関数で明示的に値を返すようにしてcontinueAsNewにyieldを付けてあげることで正常に動作しました。

これに関しては durable-functions パッケージにパッチが当たればいずれなくなる問題なので待つか、上記のような対処法で解決しましょう。

おわりに

C# とかでのDurableFunctionsの情報は調べれば割と出てくるのですが、JSのハマってしまった情報とかはあまり出てこなくて解決するのが大変でした… この記事を見た方はぜひJSでDurableFunctions使って色々とハマりそうな点を見つけて共有していってください。

おまけとか追記

JavaScript + Webpack なファイルを Azure Functions で起動する時の function.json とwebpack.config.js の libraryTarget の対応 · GitHub

そう言えば今年もMicrosoft MVP Developer Technologiesを受賞できました。 更新頻度は低いですが、誰かの役に立てるような情報を発信したり出来るよう頑張りたいと思います。