Cosmos DBエミュレーターの変更フィードをトリガーにしたAzure Functionsを作成する

2022/06/10に公開

概要

開発チームの川江です。弊社は長年 .NET と Azure を基盤としたシステム開発を行っており、現在はCosmos DB を用いたイベントソーシングに積極的に取り組んでいます。

この記事では、Azure Cosmos DB の変更フィード(Change Feed)をトリガーとして実行する Azure Functions を Visual Studio 2022 で作成し、且つデバッグ実行目的で Azure にデプロイせずローカル環境だけで動かすためのステップを紹介します。
https://docs.microsoft.com/ja-jp/azure/cosmos-db/change-feed

必要な環境

以下のものをローカル環境にインストールします。

プロジェクトの作成

Visual Studio 2022 を起動し、新しいプロジェクトの作成に進みます。表示されるプロジェクトテンプレートの中からAzure Functionsを選択します。言語はC#を選択します。

プロジェクトに適当な名前をつけて、任意のフォルダーに保存します。

次画面追加情報の2つ目の項目FunctionCosmos DB Triggerを選択します。また、作成したプロジェクトをローカルで実行するために、ランタイムストレージアカウントに Azurite を使用するにチェックが入っていることを確認します。Azurite は、以前に Azure サービスを開発する際に使用していた Azure ストレージエミュレーターに代わる新しいエミュレーターで、Visual Studio 2022 では自動的に使用できるようになっています。
その他の設定値は後から変更しますので、デフォルトのままで構いません。

プロジェクトを作成すると、Visual Studio のメイン画面が開いた後、依存関係に接続するダイアログが表示されます。このダイアログに沿って設定すれば、監視対象の Cosmos DB へ簡単に接続できます。しかし今回は、ローカル環境にインストールしたエミュレーターへ接続する設定を後ほど自分で定義しますので、キャンセルを押してダイアログを閉じます。

ここで作成されたプロジェクトを確認しましょう。

環境によっては、型または名前空間の名前が見つかりません、というエラーが表示されることがあります。その場合は、プロジェクトのNuGetパッケージの管理...を開いて、以下の2つのパッケージがなければ追加します。

ソースコードの画面からエラー表示が消えたら、プロジェクトの作成は完了です。

生成されたコードを見る

次のステップに進む前に、生成されたコードを見てみましょう。

Function1.cs
public static class Function1
{
    [FunctionName("Function1")]
    public static void Run([CosmosDBTrigger(
        databaseName: "databaseName",
        collectionName: "collectionName",
        ConnectionStringSetting = "",
        LeaseCollectionName = "leases")]IReadOnlyList<Document> input,
        ILogger log)
    {
        if (input != null && input.Count > 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}

テンプレートによって静的メソッドを一つ持った静的クラスが追加されています。なおここのクラスとメソッドが静的つまり static である必要はありません。依存関係の挿入を使用する場合などは、static ではないクラスとして定義します。また、デフォルトではメソッドがvoid型ですが、必要に応じて非同期メソッドに変更しても問題ありません。

Visual Studio 上の C# で Azure Functions を作成する場合、実行する関数(メソッド)には、何をトリガーとしてそのメソッドを実行するかを定義するための属性を書きます。ここでは、さきほどのプロジェクト作成時にCosmos DB Triggerを選択しましたので、CosmosDBTrigger 属性となっています。この CosmosDBTrigger 属性の各プロパティによって、変更をリッスンつまり監視するための対象を定義します。

CosmosDBTrigger 属性のプロパティ

自動で追加されるプロパティは以下のとおりです。

プロパティ 説明
databaseName 変更を監視する対象の Cosmos DB のデータベース名。
collectionName 変更を監視する対象の Cosmos DB のコンテナー名。
ConnectionStringSetting Cosmos DB エミュレーターないしは Azure 上の Cosmos DB アカウントに接続するための接続文字列を格納している設定の参照名。接続文字列そのものではない詳細は後述。
LeaseCollectionName 変更フィードを使用するために監視対象とは別に必要になるリースコンテナーの名前。

これらのプロパティに自分の環境に合った値を渡すことで、監視対象のDBに変更があった場合に該当のメソッドを実行できます。記事の後半部分で実際にコードを修正します。

CosmosDBTrigger 属性が持つその他のプロパティは下記のページで確認できます。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-cosmosdb-v2-trigger?tabs=in-process%2Cextensionv4&pivots=programming-language-csharp#attributes

Cosmos DB エミュレーターに監視対象コンテナーを作成する

次に、Cosmos DB エミュレーターに監視対象のコンテナーを追加します。コンテナーとは RDB におけるテーブルのようなものです。もしすでに監視対象のコンテナーがあるという場合には読み飛ばしていただいて構いません。

まず、Windows のタスクバーに表示されている Cosmos DB エミュレーターのアイコンをクリックし、そこから Data Explorer を開きます。ブラウザーでAzure Cosmos DB Emulatorが表示されます。このQuickstartページで表示されるPrimary Connection Stringは後ほど設定に使用します。

次に左のメニューでExplorerを選択します。このページでエミュレーターのコンテナーやアイテムを操作します。コンテナーを追加するにはページ左上にあるNew Containerを押します。

ページ右側に、新しいデータベースとその中に配置するコンテナーを作成するためのダイアログが表示されます。それぞれの設定値についてはマイクロソフトのドキュメント等をご覧になってください。ここでは以下の3つのみを指定し、それ以外はデフォルトのままにします。

設定 説明
Database id データベースの名前。ここではsampleDbとする。
Container id コンテナーの名前。ここではsampleItemsとする。
Partition key アイテム内でパーティションキーとして使用する要素名。ここではcategoryとしておく。

これで、新規データベースと、監視対象となるコンテナーを作成できました。

Cosmos DB 変更フィードトリガーで Azure Functions を起動するよう設定を変更する

次に、作成した監視対象の Cosmos DB コンテナーにアイテムが追加されたり変更されたりした際に Azure Functions を実行できるよう、 CosmosDBTrigge 属性の各パラメーターに渡す値を変更します。

local.settings.json に接続文字列を追加する

前述のように、Cosmos DB への接続文字列は CosmosDBTrigge 属性のプロパティに直接書くことができません。それでまずは接続文字列を local.settings.json に追加します。Visual Studio のソリューションエクスプローラーからlocal.settings.jsonを開き、Values設定の下に、任意の名前で要素を一つ追加します。ここではCosmosDbConnectionStringとしておきます。そして、さきほど開いた Cosmos DB Data Explorer の Quickstart ページにあるPrimary Connection Stringの文字列をコピーして、要素の値として貼り付けます。

local.settings.json
{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "CosmosDbConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
    }
}

CosmosDBTrigge 属性のプロパティ値を変更する

次に、コードの CosmosDBTrigge 属性を修正します。databaseNameおよびcollectionNameを、エミュレーターに作成した Cosmos DB のデータベース名とコンテナー名に書き換えます。またConnectionStringSettingプロパティの値を、local.settings.jsonに追加した要素名に変更します。LeaseCollectionNameプロパティは特に理由がなければこのままで構いません

Function1.cs
public static class Function1
{
    [FunctionName("Function1")]
    public static void Run([CosmosDBTrigger(
        databaseName: "sampleDb",
        collectionName: "sampleItems",
        ConnectionStringSetting = "CosmosDbConnectionString",
        LeaseCollectionName = "leases")]IReadOnlyList<Document> input,
        ILogger log)
    {
        if (input != null && input.Count > 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}

これで自分の環境に合わせて設定を変更できました。

リースコンテナーを自動追加する

しかしここで「さっき監視対象のコンテナーは作成したけど、リースコンテナーはまだなのでは?」と思われたかもしれません。確かにこのまま実行しても、リースコンテナーがないので例外が出てしまいます。そこで、Azure Functions 実行時にリースコンテナーを自動で追加するよう、CosmosDBTrigger 属性の CreateLeaseCollectionIfNotExistsプロパティをtrueに変更します。

Function1.cs
public static class Function1
{
    [FunctionName("Function1")]
    public static void Run([CosmosDBTrigger(
        databaseName: "sampleDb",
        collectionName: "sampleItems",
        ConnectionStringSetting = "CosmosDbConnectionString",
        LeaseCollectionName = "leases",
        CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> input,
        ILogger log)
    {
        if (input != null && input.Count > 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}

リースコンテナーが自動で追加されたことを確認する

これでリースコンテナーが追加されるのか実際に Azure Functions を起動して確認してみましょう。Visual Studio でデバッグの開始を実行します。コマンドプロンプトが開かれ、以下のように表示されていれば正常に実行できています。もし何らかの例外があれば、赤い色で例外メッセージが出力されます。

次に、Cosmos DB の Data Explorer を開きます。先程作成したsampleDbデータベースの中にleasesコンテナーが追加されていることを確認できました。

Cosmos DB にアイテムを追加して Azure Functions が実行されることを確認する

さて、これで準備が整いましたので、実際に Cosmos DB の監視対象コンテナーにアイテムを追加して、Azure Functions が実行されるかどうかを確認しましょう。

まず前項で行ったように、 Azure Functions をデバッグ実行している、つまり起動している状態にします。

次に、Cosmos DB エミュレーターの Data Explorer で、監視対象のコンテナー下にあるItemsという項目を開きます。続いてページ上部のNew Itemをクリックします。そうすると、以下のように新しいアイテムを編集できるようになります。ここではこのままSaveボタンを押してアイテムを保存します。

起動中である Azure Functions の状態を表示しているコマンドプロンプトを見てみましょう。そうすると以下のとおり、Functions1という名前のファンクションが実行されたことと、コードからログが出力されたことを確認できます。

メソッド内にブレークポイントを設定し、Cosmos DB にアイテムを追加したり変更したりすれば、きちんと止まってくれます。

Cosmos DB 変更フィードの可能性

Cosmos DB の変更フィードをトリガーとした Azure Functions は様々なユースケースで活用できます。システム開発では、

  • 注文が確定したらメールを送信する。
  • 入金記録が確認できたら仕訳データを作る。

といった、何かのイベントを引き金として始まる非同期的な処理を実装しなければならないことがよくあります。こうしたイベント駆動型アーキテクチャの実現には、メッセージやイベントの配信がよく用いられます。しかし、ユースケースによってはこの Cosmos DB の変更フィードと Azure Functions を組み合わせるだけで非同期処理を簡単に実現できます。イベントソーシングと組み合わせるとこれはさらに容易になります。Cosmos DB に記録するすべてのイベントの中から特定の型のものだけをリッスンし、非同期で実行したい処理を Azure Functions に書くことで、簡単にイベント駆動型のシステムを構築することができます。

今後の執筆予定

この記事では Visual Studio のテンプレートを使って作成したプロジェクトに最小限の設定をし、ローカルで Azure Functions を動かしただけですが、実際の開発ではいろいろな課題が出てきます。例えば、

  • データベース名などの設定をコード上にリテラル値として書くのではなく、接続文字列のように設定ファイルに記述したい。
  • 一つの変更から複数の Azure Functions を起動したい。

などです。今後このブログで随時取り上げていきたいと思っています。

ジェイテックジャパンブログ

Discussion