Azure Cosmos DB の無料版が出たので相性最高の Azure Functions と合わせて使ってみた

無料版が出ましたね！！
Publickey さんでも取り上げてもらってます。
[マイクロソフト、「Azure Cosmos DB」がずっと無料で使える「Free Tier」を発表。地球規模の分散データベースを最大5GBまで](https://www.publickey1.jp/blog/20/azure_cosmos_dbfree_tier5gb.html)
## Cosmos DB とは
Azure で一番強い NoSQL DB だと思ってます。色々な DB のモードがあって

- SQL API
- Cssandra API
- MongoDB API
- Gremlin API
- テーブル API
- etcd API

の中から選べます。私は SQL API が好きというか基本的に SQL API しか使ったことない感じです。
SQL API は JSON でデータが格納されて SQL で問い合わせ出来る感じです。（雑な説明）

## 作ってみよう
[Azure ポータル](https://portal.azure.com) から Cosmos DB を作成します。作成するときに「Apply Free Tier Discount」という項目が出来ていて Apply にするとフリーで行けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/b1d8f1de-2fd3-4d8d-7af4-01ab0ffca76b.png)

次に Cosmos DB と最高に相性がいい Azure のサーバーレスプラットフォームの Azure Functions で行ってみようと思います。普段は C# でやるのですが勉強もかねて TypeScript でやってみようと思います。

Visual Studio Code に Azure Functions の拡張機能をつっこんで環境構築をします。細かいセットアップ方法は公式に譲りたいと思います。

- [クイック スタート:Visual Studio Code を使用して Azure Functions プロジェクトを作成する](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-typescript)

Visual Studio Code からプロジェクトを作ると Cosmos DB Trigger の関数も一緒に作れるのでさくっと作ると以下のようなコードが生成されます。Cosmos DB Trigger を使うと Cosmos DB の指定した DB のコレクションにデータの追加・更新があったら動く関数が作れます。

```typescript
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;
```

作成時に適当に接続文字列の設定を `DefaultCosmosDbConnectionString` と指定したので、作成した関数の `function.json` に以下のような定義が出来てるはずです。

```json
{
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "DefaultCosmosDbConnectionString",
      "databaseName": "sampledb",
      "collectionName": "items",
      "createLeaseCollectionIfNotExists": "true"
    }
  ],
  "scriptFile": "../dist/HelloCosmos/index.js"
}
```

Azure Functions は、こんな感じでスクリプトファイルに定義された関数と、関数がどういうタイミングで実行するかという内容と定義した `function.json` で構成されます。

Azure で実行するときはポータルから設定したりできるのですが、ローカル実行時は `local.settings.json` というファイルにローカル開発用の接続文字列（Cosmos DB にはローカルエミュレーターがあります）を指定します。今回は作成した無料の Cosmos DB のプライマリ接続文字列をゲットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/c3f303ed-3542-06f2-e275-f6a138dec566.png)

`local.settings.json` に上の `function.json` の `connectionStringSetting` に書いてある `DefaultCosmosDbConnectionString` という名前の設定を追加します。こんな感じで

```json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "DefaultCosmosDbConnectionString": "AccountEndpoint=https://xxxx.documents.azure.com:443/;AccountKey=xxxx;"
  }
}
```

この段階では実行してもエラーになります。まだ Cosmos DB を作っただけで sampledb や items コレクションは作られていません。ということでさくっと作りましょう。
Visual Studio Code には Cosmos DB 拡張機能があるので、これを使うと Visual Studio Code からさくっと色々操作できます。便利。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/ea0beaea-a1d6-7656-ff27-f0fd11cd227f.png)

作りました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/ec4e567d-cace-33d6-e992-43e03ae51a72.png)

コレクションを作るときに RU を入れるのを求められて 1000 が下限だったので 1000 と入れたのですが Cosmos DB の無料版は 400 RU が限界だった気がするのでちょっと不安。ポータルでスケールの設定を見てみると 1000 になってたので 400 にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/8692adf2-e423-cb8d-ad85-097a7bf66a5c.png)

ポータルのデータエクスプローラーだと 400 から行けるので、コレクション作るときは今はポータルからのほうが安心っぽいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/e9e86faf-6926-6ae3-e57f-26db6b23b6f9.png)

これで Visual Studio Code からローカル実行するとエラー無く起動します。

Visual Studio Code から Cosmos DB のドキュメント（DB のレコードに該当する）が作れます。作ると ID と partitionKey の入力を求められるので適当に入力して作成します。作成後、value プロパティに Hello world を突っ込みます。

```json
{
  "id": "1",
  "partitionKey": "pk1",
  "_rid": "QulnAJfjx5kBAAAAAAAAAA==",
  "_self": "dbs/QulnAA==/colls/QulnAJfjx5k=/docs/QulnAJfjx5kBAAAAAAAAAA==/",
  "_etag": "\"0000ea1f-0000-2300-0000-5e68c46e0000\"",
  "_attachments": "attachments/",
  "value": "Hello world",
  "_ts": 1583924334
}
```

これでドキュメントの作成、ドキュメントの更新の 2 回の更新処理が行われています。なので、Cosmos DB Trigger の関数が 2 回動いています。Visual Studio Code のターミナルを見てみると以下のように 2 回分の実行があったことがわかりますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/bba8f1da-0012-08fe-b8f6-3319d595a1b4.png)

データ追加・更新のタイミングで処理を行うことが出来たのでデータの追加をしてみたいと思います。Cosmso DB Trigger のドキュメントは下のリンクなので、そこを参考にやっていきましょう。

- [Azure Functions 2.x での Azure Cosmos DB の出力バインド](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-cosmosdb-v2-output?tabs=javascript)

`function.json` に Cosmos DB への出力をする定義を追加します。

```json
{
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "DefaultCosmosDbConnectionString",
      "databaseName": "sampledb",
      "collectionName": "items",
      "createLeaseCollectionIfNotExists": "true"
    },
    {
      "type": "cosmosDB",
      "direction": "out",
      "name": "logsDocuments",
      "databaseName": "sampledb",
      "collectionName": "logsDocuments",
      "createIfNotExists": "true",
      "partitionKey": "/partitionKey",
      "collectionThroughput": "400",
      "connectionStringSetting": "DefaultCosmosDbConnectionString"
    }
  ],
  "scriptFile": "../dist/HelloCosmos/index.js"
}
```

関数のコードを更新します。せっかくなんでちょっとタイプセーフな感じで

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

type Item = { id: string, value: string, partitionKey: string }
type Log = { id: string, message: string, partitionKey: string, items: Item[] }
interface TypesafeContext extends Context {
    bindings: { logsDocuments: Log | Log[] }
}

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

    context.bindings.logsDocuments = {
        id: '', 
        message: `cosmosDBTrigger invoked with ${documents.length} items.`, 
        partitionKey: 'pk', 
        items: documents 
    };
}

export default cosmosDBTrigger;
```

これで、適当に items コレクションに値を更新か追加すると関数が動いて Cosmos DB に logsDocuments というコレクションが追加されます。そして、こんな雰囲気のデータが追加されています。

```json
{
    "id": "0def36d5-db85-4434-a738-f7d3aec6820c",
    "message": "cosmosDBTrigger invoked with 1 items.",
    "partitionKey": "pk",
    "items": [
        {
            "id": "1",
            "_rid": "QulnAJfjx5kBAAAAAAAAAA==",
            "_self": "dbs/QulnAA==/colls/QulnAJfjx5k=/docs/QulnAJfjx5kBAAAAAAAAAA==/",
            "_ts": 1583926160,
            "_etag": "\"00006220-0000-2300-0000-5e68cb900000\"",
            "partitionKey": "pk1",
            "value": "Hello world moe",
            "_lsn": 12,
            "_metadata": {}
        }
    ],
    "_rid": "QulnAKA9GCMCAAAAAAAAAA==",
    "_self": "dbs/QulnAA==/colls/QulnAKA9GCM=/docs/QulnAKA9GCMCAAAAAAAAAA==/",
    "_etag": "\"0100ba79-0000-2300-0000-5e68cb910000\"",
    "_attachments": "attachments/",
    "_ts": 1583926161
}
```

## Azure Functions も無料枠あります

100 万回実行まで毎月無料らしい（実質無料では）
書いたプログラムをデプロイすれば無料で Cosmos DB をストレージとして API を作ったりサーバーレスなイベントドリブンのシステムを作ったりとかできそうですね。

## まとめ

Azure Functions + Cosmos DB を使うといい感じなものが無料でお試し出来そうなのでワクワクしてます。あとはフロントの SPA とかを何処にホストするかですねぇ。個人的には、Windows やモバイルのネイティブアプリでもいいのですが世間的には SPA だよなぁ。

## 追記

Azure Functions からじゃなくても JavaScript 用の SDK があるので使ってみた記事も書いてみました。

[Azure Cosmos DB の無料枠が出来たので Cosmos DB に TypeScript から繋いで SQL 投げてみた](https://qiita.com/okazuki/items/3db3773babe9e20606da)