Azure Functions Cosmos DB Trigger を TypeScript で実装する (平成版)

Azure Functions ではさまざまなトリガーを使うことができますが、中でも気に入っているのが Cosmos DB Trigger です。これを気に入っている最大の理由は、クラウドアーキテクチャの代表的なパターンである コマンド クエリ責務分離 (CQRS) パターンイベントソーシング パターン を実現するために欠かせない技術だからです。
コミッターをやらせてもらっている intelligent-retail/smart-store でも Cosmos DB Trigger を使っていて、在庫管理アーキテクチャを実現するキーテクノロジとなっています。

最近の更新

Cosmos DB Trigger 自体は登場からもう1年以上経過しているので 使っている人もそれなり増えてきた印象がありますが、初期の頃からいくつか更新(改善)されている点があるので紹介したいと思います。

まず、何と言ってもコスト面の改善です。これまで Cosmos DB Trigger を使うには、ChangeFeed の状態管理を行うためのコンテナー(leaseコレクション)に追加コストを払う必要がありました。しかし、Cosmos DB で データベースレベルでの共有スループット がサポートされたことで、leaseコレクションをデータを格納しているコンテナーと同一データベースに含めることができるようになり、追加コスト不要でプロビジョニングすることが可能となりました。

データベースレベルの共有スループット登場直後は、Azure Fucntions の Extensions 側が対応していなかったため、 leaseコレクションを含めることができなかったのですが、Cosmos DB プロダクトチームの Matías QuarantaPull Request #520 · Azure/azure-webjobs-sdk-extensions で対応してくれたため、現在では問題なく設定ができるようになっています。
なお、Microsoft.Azure.WebJobs.Extensions.CosmosDB のバージョンを 3.0.3 以上でこの問題に対応するようになっているので、csproj で Extensions のバージョンを確認しておきましょう。

Cosmos SDKもメジャーバージョンアップが行われています。まず Node.js の Azure/azure-cosmos-js が V2系 となり、 async/await が使えるようになる等かなり大幅に更新されています。App Service チーム から移ってきた Christopher Anderson が中心になってコードを全面書き換えした効果が大きいと思われます。.NET(C#) SDK についても、V3系となり(執筆時点でプレビュー)、 Node.js 版の V2 と同様にほぼ全面書き換えがされているようです。なお、.NET SDK は V3 からようやくメインのソースがOSSとして公開された点も歓迎すべきでしょう。

Cosmos DB のプロビジョニング

Cosmos DB をデータベースレベルの共有スループットでプロビジョニングしてみます。Azure CLI で作成する方法は以下の通りです。
なお、Cosmos DB アカウントが作成されている前提にしていますので、作成していない場合は、Azure CLI の Microsoft Docs を参照して下さい。

az cosmosdb database create \
--resource-group 'lab-rg' \
--name 'myk-cosmos-001' \
--db-name 'mykdb' \
--throughput 1000

これで、データベース mykdb 配下に作成するコンテナー(コレクション)は、データベース内で RU が共有されるようになりました(この例は 1000RU を複数コンテナーで共有する設定)。

Azure Functions 側の実装

Azure Functions 側の実装・設定上のポイントを紹介しておきます。設定ファイルの見やすさから Node.js の Azrue Functions で説明します。

func init

Node.js -> TypeScript と選択します

func new

Azure Cosmos DB trigger を選択して関数を新規作成します。

生成された extensions.csprojMicrosoft.Azure.WebJobs.Extensions.CosmosDB のバージョンが 3.0.3 以上になっていなければ、バージョンを修正してから再度 func extensions install を実行して下さい。 npm install を実行してJS側のリストアも忘れずに。

CLI で生成された設定ファイル function.json はこんな感じになるはずです。

{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "myk-cosmos-001_DOCUMENTDB",
"databaseName": "mykdb",
"collectionName": "mykcol",
"createLeaseCollectionIfNotExists": "true"
}
],
"scriptFile": "../dist/CosmosTrigger/index.js"
}

ポイントは、leaseコレクションの作成先DB名を明示的に指定しないことと、 createLeaseCollectionIfNotExiststrue にしておくことです。これで初回実行時にデータ取得元のコレクションと同一DBにleaseコレクションが生成され、追加のコンテナー(コレクション)利用コストが発生しなくなります。

CLIで自動生成されるコードは以下のような感じです。

import { AzureFunction, Context } from "@azure/functions"

const cosmosDBTrigger: AzureFunction = async function (context: Context, documents: any[]): Promise<void> {
if (!!documents && documents.length > 0) {
context.log('Document Id: ', documents[0].id);
}
}

export default cosmosDBTrigger;

TypeScript なのできちんと型が割り当てられています( AzureFunction 型とかアツいです)。 documents の型が any[] になっていますが、ここはトリガーでバインドされるJSONの型に従って自分でクラスを定義しましょう。

TypeScriptのプロジェクトなので、コンパイル(トランスパイル)すると、

npm run build

/dist 配下にJSの実行コードが生成されます。
あとは、いつものように func host start で Azure Functions Core Tooles を使ったデバッグ実行ができます。

TypeScript自体は1.0がリリースされてから数年経過しますが、今年は Vue.js の3系でも全面採用される予定ですし、今回のように Azure Functions でも使えるようになったので、個人的には改めて TypeScript を使い倒していくことになりそうです。 TypeScript のコミュニティも企画していたりするので、お楽しみに!