Azure Functions×Cosmos DBを使って色々なアプリケーションを作ってみよう

## はじめに

[前回の記事](https://qiita.com/maplejava/items/a226445ae761e3311e8f)からの続きです。Azure FunctionsとCosmos DBを組み合わせてイベント駆動型の小規模なアプリケーションを作ってみましょう。
HTTPトリガー、タイマートリガーを紹介し、最後にCosmosDBのレコードが更新されたことをトリガーとする[変更フィード](https://learn.microsoft.com/ja-jp/azure/cosmos-db/change-feed)という仕組みを使ってアプリケーションを作ってみます。

## VSCodeの拡張機能インストール
azure databases 拡張機能のインストール

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/5fa3d374-5f14-3dbb-134f-de9b9a85487c.png)

## Azure Cosmos DBアカウントの作成
「Azure Databases: Create Server…」を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/09176b34-5974-8465-422e-e8abb84694f4.png)

以下の通り作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/532945e7-26c7-61d1-2d4e-6235360fdb57.png)

作成完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/24adee87-b3e9-90f6-a026-a2538699dc1e.png)

## Cosmos DBを作成する
「azure」タブから先ほど作成したcosmos DBを選択し「create database」を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/f288a8a1-10c9-14b2-cb00-4030111db41d.png)

下記の情報を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/2ab3e74b-feb1-572d-200a-7fa53fc8b6ff.png)

Cosmos DBアカウントを選択し「Copy Connection String」で接続情報をコピーする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/7f87bb87-493d-371c-498d-cac5a9daf950.png)

以降の細かい手順は以下に沿って実施する。

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-add-output-binding-cosmos-db-vs-code?pivots=programming-language-python&tabs=isolated-process%2Cv2#update-your-function-app-settings

流れとしては、CosmosDBの接続情報をローカルにダウンロードしてきて、ローカル⇔CosmosDBも接続しつつ、デプロイ環境⇔CosmosDBもつなぐ。

コード
```python
import azure.functions as func
import logging

app = func.FunctionApp()

@app.function_name(name="HttpTrigger1")
@app.route(route="hello", auth_level=func.AuthLevel.ANONYMOUS)
@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")
@app.cosmos_db_output(arg_name="outputDocument", database_name="my-database", container_name="my-container", connection="CosmosDbConnectionSetting")
def test_function(req: func.HttpRequest, msg: func.Out[func.QueueMessage],
    outputDocument: func.Out[func.Document]) -> func.HttpResponse:
     logging.info('Python HTTP trigger function processed a request.')
     logging.info('Python Cosmos DB trigger function processed a request.')
     name = req.params.get('name')
     if not name:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('name')

     if name:
        outputDocument.set(func.Document.from_dict({"id": name}))
        msg.set(name)
        return func.HttpResponse(f"Hello {name}!")
     else:
        return func.HttpResponse(
                    "Please pass a name on the query string or in the request body",
                    status_code=400
                )
```

動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/a056a98a-e02a-2d16-83e5-34e4d62f59cc.png)


HTTPリクエストにnameパラメータが入っていると、そのパラメータに入っている値をCosmos DBに登録することができるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/c0a613e9-ee57-0f64-d2ea-2d7fd0be80d1.png)

## タイマートリガーにしてみる
HTTPトリガーではなく、定期的に自動実行してCosmosDBに登録してくれるタイマートリガーにしてみる。

コード

```python
import logging
import azure.functions as func
import datetime

app = func.FunctionApp()

@app.schedule(schedule="0 */1 * * * *", arg_name="myTimer", run_on_startup=True,
              use_monitor=False) 
@app.cosmos_db_output(arg_name="outputDocument", database_name="my-database", container_name="my-container", connection="CosmosDbConnectionSetting")
def timer_trigger01(myTimer: func.TimerRequest, outputDocument: func.Out[func.Document]) -> None:
    if myTimer.past_due:
        logging.info('The timer is past due!')
    current_time = datetime.datetime.utcnow().isoformat()
    logging.info('Python timer trigger function executed.')
    outputDocument.set(func.Document.from_dict({"id": current_time}))
```

VSCodeでAzure Functions作成時に、既定のタイマートリガー関数を作成してから以下の変更を加えた。

- 関数の引数に「OutputDocument」を追加。なお「→ None」の部分は返り値の型なので、タイマートリガーの場合はNoneでよい
- outputDocument.set()関数を追加

カンタン！

動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/20876152-3b55-3d1f-c31d-11103b44c844.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/cb820c53-6c86-1388-8635-748778d8ba11.png)
ちゃんとCosmosDBに値が入っている。

## Cosmos DB triggerにしてみる
Azure FunctionsのCosmos DB Triggerを使えば、指定したCosmos DB内の特定のテーブルのレコードが変更された場合に関数を実行することができるようになります（変更フィード）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/3b06027d-075a-80a8-6e93-5c5d8db85b12.png)
参考

https://learn.microsoft.com/ja-jp/azure/cosmos-db/nosql/change-feed-functions

コード
```python
import azure.functions as func
import logging

app = func.FunctionApp()

@app.cosmos_db_trigger(arg_name="azcosmosdb", container_name="my-container",
                        database_name="my-database", connection="azfoocosmosdb02_DOCUMENTDB",
                        LeaseContainerName = "leases", CreateLeaseContainerIfNotExists = "true")  
def cosmosdb_trigger03(azcosmosdb: func.DocumentList):
    logging.info('Python CosmosDB triggered.')
```
VSCodeでAzure Functions作成時に、既定のCosmos Trigger関数を作成してから以下の変更を加えた。
- 「LeaseContainerName = "leases", CreateLeaseContainerIfNotExists = "true"」をデフォルトのコードに追加（しないと以下のエラーになる）

エラー
```text
This builder instance has already been used to build a processor. Create a new instance to
 build another.
```

https://stackoverflow.com/questions/77718371/azure-cosmos-db-emulator-error-with-azure-function

また、local.setteing.jsonの内容はあくまでもローカルでの実行時の設定値であるため、デプロイしたときは「azfoocosmosdb02_DOCUMENTDB」とその値をF1キー→「Azure functions : add new settings」から追加する必要はあることに注意（ポータルからも追加可能）。

https://qiita.com/takmot/items/13e5dd1d34ddf100ddb5#環境変数の追加

## おわりに
いかがでしたでしょうか。Azure FunctionsとCosmos DBの様々な機能を使えば、作りたい基盤を簡単に構築することができます。
面倒な部分はAzureに任せて、アプリケーションの製造に集中しましょう！