前回 Slack 側の準備と送信したいメッセージの json フォーマットを作りました。
今回は本題の Azure Functions の Custom Bindings を実装します。
作った順にコードを解説していきますが、コード全部は下の方にリンク張ってます。
Azure Functions の Bindings とは
まず、Bindings とかトリガーとはって話ですが、Azure Functions で HttpTrigger や QueueTrigger を使うとき、Attribute を使ってますよね。あれです。ドキュメントはこちら。
こいつを自作しましょう。
たいして必要ないのに時間を無駄にして作るその心意気、その魂こそが Custom Bindings です(...ちょっと嘘つきました)。
Custom Bindings を作ることで、使う側は Attribute をつけてちょちょいってやるだけでやりたいことができるようになります。Power Automate や Logic Apps でいうコネクターを自作するようなものですね。
前回のブログで Slack で通知を受け取る準備し、Functions で送信を試しちゃってるので、それで実現できてるのに敢えて作るのです。
Custom Bindings の作成
プロジェクトの作成
.NET Standard のクラスライブラリーを作成をさくっとします。
で、NuGet をインストールします。以下の3つが最低限必要です。現時点で最新のものを入れます。
- Microsoft.Azure.Functions.Extensions : v1.0.0
- Microsoft.Azure.WebJobs : v3.0.14
- Microsoft.Extensions.Http: v3.1.1
- System.Text.Json: v4.7
以降は、この順番で作っていきます。
- binding attribute(s) の実装
- binding rules の実装
- Converters の追加
Binding attribute(s) の実装
Attribute の作成です。Attribute は Functions のメソッドにつけるこれです。
今回は環境変数から Slack の IncommingWebhook の URL を取ってくる前提です。上の図のように [Slack(IncomingWebhookUrl = "Slack:IncomingWebhookUrl")]
と書くことで環境変数のキー Slack:IncomingWebhookUrl
の値がセットされます。
私のマジでやりたいことは、URL も動的に取得するパターンがですが、環境変数の情報とるコードがなくなるとサンプルとして盛り上がりに欠けるのでこの前提にしましたw。
コードはこれだけです。
ざっくりな解説です。
- 6-7行目: class につけた Attribute はお作法です。
- 8行目: class は
Attribute
を継承させる必要があります。 - 10行目: ロパティに
[AppSetting]
をつけることで環境変数からとってきてくれます。Default をつけてあげると Functions 側のメソッドでDefault 値を使うなら指定を省略できたりします。
と、圧倒的に雑な解説ですが、使ってない部分で重要な要素がたくさんあるので、実際に開発するときはこちらの公式のドキュメントを読みましょう。
Slack への通知のコア実装
Slack へ通知する部分を実装します。前回のブログの 余談: Azure Functions で送信してみる で書いた通りの実装をちょっとまともに書くだけです。
SlackClient
というクラス名にしました。インターフェース ISlackClient
も用意してます。
コンストラクターで Slack の送信先の URL と HttpClientFactory を受け取ってみました。
のちほど使う Factory 用のクラスも作っておきます。
Collector の実装
Collector は、以下図のように Functions のメソッドの引数で使うやつです。引数で DI で受け取り、IAsyncCollector
インターフェースで実装されている AddAsync
メソッドで実行するという流れです。
今回は非同期で送信するための IAsyncCollector
を使ってますが、他にもいくつか種類があり、以下リンクで確認できます。
今回の実装はこんな感じ。
先ほど作った SlackClient
をコンストラクターで受け取り、IAsyncCollector
インターフェースのメソッド AddAsync
で Slack への送信を実行しているのみです。ちなみに IAsyncCollector
インターフェースの Generics は、AddAsync
メソッドの引数の型になります。
SlackClient クラスのメソッドを呼び出すだけにしてるこのクラスのテストはしないって方針です。まー今回くらいシンプルならここに直接グダグダとコード書いてもいいんですけどー気持ち悪いのでやめときます。
Binding rules - ExtensionConfigProvider の作成
先ほど作った Collector を生成するための Configuration とでもいうものでしょうか。 IExtensionConfigProvider
インターフェースを実装したクラスをこんな感じで作りました。
20-25行目: コンストラクターで必要なものを受け取ってます。ここは DI で受け取るので、後述する DI の設定で必要なものだと思って下さい。今回は環境変数を詰め込んで構成しておく
IOptions<T>
を書いてますが...今回はいらんといえばいらん、今後の自分の参考のために書いただけです。28-35行目:
IExtensionConfigProvider
インターフェースで定義されてるメソッドの実装です。32行目で Attribute の Bind、34行目で Collector のBind を定義してるだけなので、言われてみるとシンプルなものです。
実際になにか作るときはこちらの公式ドキュメントを読んでからやると良いです。
Converter の作成
Converter は、Functions で IAsyncCollector のインスタンスが必要になったときに生成して渡すためのものです。これは、IConverter<TInput, TOutput>
インターフェースの実装のお作法なだけで、こう書くだけって感じです。
公式のドキュメントでもうちょいごちゃごちゃしたことを言ってます。
DI の実装
Azure Functions における DI の実装の基本はここら辺です。
今回必要な DI は、SlackExtensionConfigProvider
の部分ですので、とりあえず拡張メソッド作ります。
書いたまんまの実装です。
あとは、Startup でこいつを呼ぶ実装をします。
Azure Functions で動作確認
Functions のプロジェクト作って適当に動かしてみましょう。実際使うときは Queue Trigger で使おうと思ってますが、このサンプルでは実行が簡単な HTTP Trigger 作ります。まずは環境変数の設定をしておきますか。local.settings.json
を開いて、プログラムで指定した Slack:IncomingWebhookUrl
というキーと値を入れます。こんな感じ。
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "Slack:IncomingWebhookUrl": "ここに slack の Incoming Webhook の URL を入れる" } }
Functions のコードはこんな感じにしました。
Function1 を呼ぶとこんな感じのシンプルなもの。
Functions2 を実行すると前回のブログで書いたような BlockKit のメッセージを送信します。
BlockKit の実装は、こんな感じで構成できるよう書きましたが、今使う部分しか実装してないです。
コード全体
簡易コード言えど、ソースをアップするとなるとテスト書いてないことに恥ずかしさを感じますよね...なのでプロジェクトだけは作りました(ダメな典型...)。
追記
動的に Slack の URL 取得(つまりはメソッドの引数から取得)してメッセージを送信できる update をしたせいで前述のコートから多少変更されています。