BEACHSIDE BLOG

Azure とか C# 好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

Slack 通知用の Azure Functions の Custom Bindings を作る (2)

前回 Slack 側の準備と送信したいメッセージの json フォーマットを作りました。

blog.beachside.dev

今回は本題の 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 のメソッドにつけるこれです。

f:id:beachside:20200205154900p:plain:w600

今回は環境変数から 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 メソッドで実行するという流れです。

f:id:beachside:20200205162552p:plain

今回は非同期で送信するための 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 の実装の基本はここら辺です。

blog.beachside.dev

今回必要な 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 を呼ぶとこんな感じのシンプルなもの。

f:id:beachside:20200205183927p:plain

Functions2 を実行すると前回のブログで書いたような BlockKit のメッセージを送信します。

f:id:beachside:20200205184101p:plain

BlockKit の実装は、こんな感じで構成できるよう書きましたが、今使う部分しか実装してないです。

f:id:beachside:20200205184228p:plain

コード全体

簡易コード言えど、ソースをアップするとなるとテスト書いてないことに恥ずかしさを感じますよね...なのでプロジェクトだけは作りました(ダメな典型...)。

追記

動的に Slack の URL 取得(つまりはメソッドの引数から取得)してメッセージを送信できる update をしたせいで前述のコートから多少変更されています。

github.com

参考

github.com

blog.beachside.dev

blog.beachside.dev