Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8832

Node.jsでDurable Functionsを使うなら、入出力はObjectにした方が安心かもという話

$
0
0

注: 本記事は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などを参照してください。

結論だけ知りたい人向け

  • アクティビティ関数の入出力バインディングに文字列や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(typeofcontext.bindings.name);context.log(context.bindings.name);returncontext.bindings.name;};

みたいにして、HelloSequenceからSayHelloに渡す値を変えてみましょう。

まず手始めに正常系で

yieldcontext.df.callActivity("E1_SayHello","Tokyo")

を実行してみます。
ログには

string
Tokyo

と表示されたと思います。
問題ないですね。

では次に数字を渡してみましょう。

yieldcontext.df.callActivity("E1_SayHello",123)

を実行してみます。
ログには

number
123

これも想定通りですね。

では数字を文字列型として渡してみます。

yieldcontext.df.callActivity("E1_SayHello",'123')

を実行してみるとログには

number
123

:thinking:
私は文字列を渡したはずだが…?となりますが、これが上記で述べた

渡ってきた値に全てに対して JSON.parseを適用します(ByteArrayを除く)。

の弊害となります。
これが大きな問題となるのが、Number型で表現できる最大最小の値を超えた時などです。
TwitterなどのAPIではidの値が非常に大きな値となっていて、JavaScriptのNumber型では表現の出来ない値になっています。
そのため id_strというidをstr型で表現した値も一緒に送信しているのですが、それをDurable Functionsで扱おうとすると上記の挙動でnumber型にparseされ、表現できない範囲の値になり正常な値が扱えなくなるというものです。

他にも不思議な挙動はいくつかあり

yieldcontext.df.callActivity("E1_SayHello",'null')

を実行してみるログには

object
null

文字列を渡しているのにnullが渡ってきたり(同様に'{}'を渡すと {}が返ってきたり)

yieldcontext.df.callActivity("E1_SayHello",true)

を実行してみるログには

string
True

と、 JSON.stringify(true)したら trueが返ってきているはずでは?と言った挙動が見られます。

このことから JSON.stringifyっぽいことをしているけれども、そうじゃない別の何かが行われているということがわかります。

このあたりで一旦grpcでゴニョゴニョする時に何かが怒ってるのかなぁとあたりは付けているのですが、調査しきれていません)

対処法

扱う対象の値によっていくつか選択肢はありますが、入力値に文字列や数値、bool値、null値をそのまま使うのではなく、

オブジェクトの値として渡す

これで解決します。

例えば

yieldcontext.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でやり取りするのも一つの手かもしれませんね


Viewing all articles
Browse latest Browse all 8832

Trending Articles