Azureには、Storage Table という機能があり単純なテーブル形式でデータが管理できます。 C# のコード的にはTableEntity を継承してデータを表現し、メソッドを組み立ててCRUDを実現できます。
さて、このStorage TableをWebJobs 並びにその実装の1つである Azure Functions で使うときについて考えてみましょう。
目次
TL;DR
Bindingには複数の課題があり、特にStorage Table や DBのバインディングはパラメーターが多く問題が起きやすい。
- Bindingは属性 + エントリーポイントのエラーのためStackTraceが取れないケースが多い
- 加えて Binding のリフレクション多用による難しさ
- StorageTableとDBは特にパラメーターが多くBinding問題に当たりやすい
- NuGetバージョンのRegression耐性の乏しさ
このため、Storage TableやDBに関してはBindingはやめてFunction内でクライアントを自前で作って安定させるほうが良いケースが多い。
他のHttpTriggerやQueue などはシンプルでトラブルが起こりにくく、自前で定型的に実装することになる機能がサポートされるので使ったほうが楽なBindingと感じます。
Binding にまつわる実装とデバッグの距離
自前コードからの呼び出し口があれば、デバッガを仕掛ける口があり距離としては近づきます。 しかし、Bindingは初期化時に起こるためデバッガとの距離が遠いという、作り上の潜在的な課題があります。
どこまで簡単にデバッグできるかはかなり重要な選択基準になるので順に見てみましょう。
属性 + エントリーポイント
Bindingは属性 + エントリーポイントのエラーのためデバッガを仕掛ける口がFunctionに開いていません。そのため、シンボルをVSで捕まえられるかが容易なデバッグの鍵となりますが、残念ながら多くの場合機能しないことが多いです。
例えば、Microsoft.Azure.WebJobs.Extensions.Storage が 3.0.1のときはシンボルも取れず デバッガはおろかStackTraceも取れません。
→ Microsoft.Azure.WebJobs.Extensions.Storage 3.0.1から3.0.5に上げることでデバッガがソース内部まで行くように改善されます。(Bindingで落ちることに変わりはない)
この状況は、Bindingのパラメーターを間違えれば容易に再現できるので、エラーから状況を把握することを試してみてください。
リフレクションの多用とRegressionの多さ
WebJobs の Binding はリフレクションを多用しており、些細なミスで機能しないことが起こりえます。
これが顕著なのが、NuGetライブラリバージョンに依存したBinding失敗でバージョンを上げると同一コードで動かないといったRegressionが過去に何度も起こっており、また現在も起こりえます。
例えば、AzureFunctions で利用するプロジェクトが複数あったときに、Microsoft.Azure.WebJobs.Extensions.Storageの下限バージョンとWindowsAzure.Storage バージョンでミスマッチを起こしているとBindingエラーが起こるようになります。(BindingRedirect ではなく、です..... )
この問題は、気づけるなら大したことはありませんが、エントリーポイントのBindingでのエラーのため、自分のパラメーターミスが原因だった時と同じエラーでもあるため気づくのはこんなんです。
結果として、Bindingでのトラブルは解消するのが難しい側面があります。
NuGetバージョンに起因した問題の再現方法
2つプロジェクトを用意し、片方はAzure funcitons のプロジェクトで Microsoft.Azure.WebJobs.Extensions.Storage を入れておきます。(例では3.0.1) もう片方は、WindowsAzure.Storage をNuGetで入れます。(何も考えずにいれると 9.3.3 が入ります)
AzureFunctions で TableBinding をかけて実行すると、即落ちてTableAttributeBindingProvider..cs がないためデバッグがその先のコードにたどり着けません。
おもむろに NuGet Package Manager を見ます。最新バージョンの Microsoft.Azure.WebJobs.Extensions.Storage (例は3.0.5) が出ているのでアップグレードします。
コードの変更なく、先程のBinding時のエラーがリフレクションでとってきた tableAttributeのRowKey が null であるためにコケるように変わります。
AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 の依存している WindowsAzure.Storage のバージョンが >= 9.3.1 となっています。
依存プロジェクトの WindowsAzure.Storage を 9.3.3 にしていました。
そこで依存プロジェクトの WindowsAzure.Storage を 9.3.1 に変更します。
AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 が依存している WindowsAzure.Storage も 9.3.1 を見るように変わったことがわかります。
コードを何も変更してないけど正常に動くことが確認できます。
問題の回避方法
今、このBinding問題が起こっている方は、多くの場合、適切なバージョンの選択により問題が回避できます。(パラメーターが正しい場合ですが)
Microsoft.Azure.WebJobs.Extensions.Storageの利用しているWindowsAzure.Storage下限バージョンに合わせる
現在 Storage に対するNuGetライブラリは、WindowsAzure.Storage から Microsoft.Azure.Storage.Xxxx への移行途中にあります。特にStorage Tableは影響度が高くしばらくは9.3.1、正しくは Microsoft.Azure.WebJobs.Extensions.Storage の依存しているバージョンに従いましょう。
依存ライブラリに注意する
NuGetバージョン問題は、依存ライブラリになっている場合は個別ライブラリのバージョンに引っ張られる、というNuGet Packageの特性により「自分の気づかない内にバージョンが変わっていた」という状況が起こりやすいようです。
特に、プロジェクト参照していると依存プロジェクトのWindowsAzure.Storageバージョンに引きずられてバージョンが狂ってより死ぬ。という。
Bindingをやめる
そもそもBindingをやめるのが、デバッガビリティ、ライブラリのバージョンに起因したRegressionから簡単に逃げる選択肢になりえます。
つまり、自分でFunction内部でクライアント(CloudTableClientなど) を生成することで、安定した利用ができるでしょう。特にパラメーターが多く問題が起きやすいTableBinding と DB Binding は Binding を行わないのは選択肢となりえます。残念ですが。
複数の方が同じ結論でクライアントを自分で生成されているようです。
Storageバインディング💩なのでTableClientをDIすればいいやって感じで解決してる(´-`)
— ゆ〜かさん (@yu_ka1984) April 2, 2019
DBやらTableやらのBindingはなからする気がない(活用したら便利なんだろうたぶん。でもワイはBindingしない
— こすもす.えび (@kosmosebi) April 2, 2019
Q&A
Table Storage のBindingは空テーブルとか作ってくれて楽なんだけど
Bindingで行われる存在しないTable作成などはTerraformなどの静的な構成管理で担保もできるので、アプリケーションだけでなく全体像でハンドルもありですしコードで担保してもいいでしょう。
Microsoft.Azure.Storage.Xxxx やWindowsAzure.Storage 9.4.xを使い始めるタイミング
Microsoft.Azure.WebJobs.Extensions.Storageに依存しましょう。なお、Storage Tableは 9.4.0 から WindowsAzure.Storage から除外されてCosmosDB ライブラリに移行します。Microsoft.Azure.WebJobs.Extensions.Storageのバージョンが9.4.xを使うまですぐに移行せずに少し様子を見たほうが良さそうです。
BindingでMSIどうなってるの?
Storage Binding はMSIによる認証にも対応していないなど、クラウド側の更新への追随がまだ未整備という側面もあります。