Node.jsでDurable Functionsを使うなら、入出力はObjectにした方が安心かもという話
注: 本記事は2020/09/28時点のものであり
https://www.npmjs.com/package/durable-functions の v1.4.3
https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/ のv2.2.2
https://github.com/Azure/azure-functions-nodejs-worker のv1.2.2
で検証及び再現した事象です。
今後の変更及び修正により挙動が変わる場合があるため、バージョンが変わっていた場合各プロダクトのIssueなどを参照してください。
追記(2020/10/08):
本日リリースされた
https://github.com/Azure/azure-functions-durable-js/pull/217
azure-functions-durable-js v1.4.4で文字列で渡した値は文字列として扱うような変更が入ったため、以下の問題は一部を除いて解消されました。
booleanについてはまだ問題が残っていますが多くのケースでの問題は解消されたでしょう。
明示的に JSON.stringify
しても、Activityでは暗黙的な変更がなされて数値として受け取っていた、みたいな使い方をしていた人は挙動が変わっているので注意しましょう。
追記終わり
結論だけ知りたい人向け
- アクティビティ関数の入出力バインディングに文字列やbooleanなどの値を渡すと意図しない変換が行われる
- オブジェクトでラップして渡せば基本的には問題ない
とりあえずこれを守っておけばハマることは少ないと思います。
起こる原因
Durable Functionsでは入出力バインディングのデータをJSON文字列としてやり取りしています(ByteArrayを除く)。
ref: Durable Functions のバインド - Azure | Microsoft Docs
関数でバインディングが行われた入力を参照する時は呼び出し元の入力が JSON.stringify
されて、更に JSON.parse
された値が渡ってくる認識して良いでしょう(厳密には異なります)。
Durable Functionsなどの実行環境とも言えるAzure Functions NodeJS Worker では渡ってきた値に全てに対して JSON.parse
を適用します(ByteArrayを除く)。
ref: https://github.com/Azure/azure-functions-nodejs-worker/blob/11303c0dcf2ddcbf876e0cc453dbe3a731b769d9/src/Context.ts#L22
………とここまで内部でだいたいどんな事が行われているか記載しましたが、正直自分も処理を追いきれておらず、各文章の末尾に「っぽいことが行われています」が付きそうな具合です。
どういうこととお思いかと思うので、実際の例を挙げてみます。
不思議な挙動のバインディング
ここでは公式サンプルのSayHelloを例にとってみます。
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_HelloSequence/index.js
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_SayHello/index.js
SayHelloを一部改変して
module.exports = function (context) {
context.log(typeof context.bindings.name);
context.log(context.bindings.name);
return context.bindings.name;
};
みたいにして、HelloSequenceからSayHelloに渡す値を変えてみましょう。
まず手始めに正常系で
yield context.df.callActivity("E1_SayHello", "Tokyo")
を実行してみます。
ログには
string
Tokyo
と表示されたと思います。
問題ないですね。
では次に数字を渡してみましょう。
yield context.df.callActivity("E1_SayHello", 123)
を実行してみます。
ログには
number
123
これも想定通りですね。
では数字を文字列型として渡してみます。
yield context.df.callActivity("E1_SayHello", '123')
を実行してみるとログには
number
123
私は文字列を渡したはずだが…?となりますが、これが上記で述べた
渡ってきた値に全てに対して
JSON.parse
を適用します(ByteArrayを除く)。
の弊害となります。
これが大きな問題となるのが、Number型で表現できる最大最小の値を超えた時などです。
TwitterなどのAPIではidの値が非常に大きな値となっていて、JavaScriptのNumber型では表現の出来ない値になっています。
そのため id_str
というidをstr型で表現した値も一緒に送信しているのですが、それをDurable Functionsで扱おうとすると上記の挙動でnumber型にparseされ、表現できない範囲の値になり正常な値が扱えなくなるというものです。
他にも不思議な挙動はいくつかあり
yield context.df.callActivity("E1_SayHello", 'null')
を実行してみるログには
object
null
文字列を渡しているのにnull
が渡ってきたり(同様に'{}'
を渡すと {}
が返ってきたり)
yield context.df.callActivity("E1_SayHello", true)
を実行してみるログには
string
True
と、 JSON.stringify(true)
したら true
が返ってきているはずでは?と言った挙動が見られます。
このことから JSON.stringify
っぽいことをしているけれども、そうじゃない別の何かが行われているということがわかります。
(このあたりで一旦grpcでゴニョゴニョする時に何かが怒ってるのかなぁとあたりは付けているのですが、調査しきれていません)
対処法
扱う対象の値によっていくつか選択肢はありますが、入力値に文字列や数値、bool値、null値をそのまま使うのではなく、
オブジェクトの値として渡す
これで解決します。
例えば
yield context.df.callActivity("E1_SayHello", { idStr: '123' })
としてSayHello関数に値を渡して、SayHello関数では
module.exports = function (context) {
...
context.log(context.bindings.name.idStr);
...
};
として受け取ると言った具合です。
オブジェクトは JSON.stringify
っぽいことをされるのではなく、JSON.stringifyが行われるため、数値は数値のまま、文字列は文字列のまま渡されるようになります。
かなりエッジケースな話ではありますが、ユーザからの入力値で文字列が来ると思っていたのに何か違うものが渡ってきているぞ、というバグにお悩みの方は上記の方法で対処してみることも考えてみてください。
なお上記現象については
https://github.com/Azure/azure-functions-durable-js/issues/215
にてFB済みです。
今後の対応を期待しましょう。
ByteArrayでやり取りするのも一つの手かもしれませんね
Author And Source
この問題について(Node.jsでDurable Functionsを使うなら、入出力はObjectにした方が安心かもという話), 我々は、より多くの情報をここで見つけました https://qiita.com/yamachu/items/97c98e41ba7088ca69e6著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .