AzureFunctions+AzureBlobStorageでアクセスカウンターの作成

Azure Functions と Azure Blob Storage の Binding 連携の勉強用に読み出されるたびにカウンターが繰り上がっていくものを作成する。

# はじめに
利用環境としては無料で検証できる Azure サンドボックス環境下で行う。
Azure サンドボックスで Functions を使うには下記にて実践できる

* [ゼロから1時間で作れるAzure Functionsハンズオン - Qiita](https://qiita.com/tkmry/items/090e652c8dd0d13c9017)

# 構成
以降の記事及び Azure Storage/Functions の構成を以下に記載する。

1. サンドボックスの有効化
1. Azure Blob Storage
    1. ストレージアカウントの作成
    1. コンテナーの作成
    1. 配置するファイル
1. Azure Functions
    1. 関数アプリ
    1. 関数
    1. 統合(Binding/function.json)
    1. Code
1. 実行画面

## 1. Azure サンドボックスの有効化

[参考](#参考) の Microsoft Learn にアクセスし演習ページからサンドボックスを有効する。有効にできたら [Azure Portal](https://portal.azure.com) にアクセスする。

## 2. Azure Blob Storage

### 2.1. ストレージアカウントの作成
以下の内容で作成する。なお、その他の設定は特に弄らない。

| 項目 | 値 |
| --- | --- |
| **プロジェクトの詳細** |  |
| 　サブスクリプション | Concierge Subscription |
| 　リソースグループ | learn-* |
| **インスタンスの詳細** |  |
| 　ストレージアカウント名 | counters |
| 　地域 | Japan Easst |
| 　パフォーマンス | Standard |
| 　冗長性 | geo 冗長ストレージ (GRS) |

### 2.2. コンテナーの作成
BLOBコンテナーを下記内容で作成する。

| 項目 | 値 |
| --- | --- |
| 名前 | access-counter |
| パブリック アクセス レベル | プライベート |

### 2.3. 配置するファイル
access-counter コンテナの直下には下記ファイルをアップロードする。

| 項目 | 値 |
| --- | --- |
| ファイル名 | counter |
| ファイルの中身 | 1 |

## 3. Azure Functions 側

### 3.1. 関数アプリ
以下で作成する。なお、ホスティングやネットワークは特に弄らない。

| 項目 | 値 |
| --- | --- |
| **プロジェクトの詳細** |  |
| 　サブスクリプション | Concierge Subscription |
| 　リソースグループ | learn-* |
| **インスタンスの詳細** |  |
| 　関数アプリ名 | hogefuga01 |
| 　公開 | コード |
| 　ランタイムスタック | Node.js |
| 　バージョン | 16 LTS |
| 　地域 | Japan Easst |
| **オペレーティングシステム** |  |
| 　オペレーティングシステム | Windows |
| **プラン** |  |
| 　プランの種類 | 消費量(サーバレス) |

### 3.2. 関数
下記トリガーの関数を作成する。

| 項目 | 値 |
| --- | --- |
| テンプレート | HTTP Trigger |
| 新しい関数(名前) | HttpTrigger1 ※他の名前でも良い |
| Authorization level | 匿名 |

### 3.3. 統合(Binding/function.json)

■ 入力の Binding の追加

| 項目 | 値 |
| --- | --- |
| **バインドの種類** | Azure Blob Storage |
| **Azure Blob Storage の詳細** |  |
| 　Storage account connection | counters_STORAGE (new) |
| 　Blob parameter name | inputBlob |
| 　Path | access-counter/{name} |

■ 出力の Binding の追加

| 項目 | 値 |
| --- | --- |
| **バインドの種類** | Azure Blob Storage |
| **Azure Blob Storage の詳細** |  |
| 　Storage account connection | counters_STORAGE |
| 　Blob parameter name | outputBlob |
| 　Path | access-counter/{name} |

■ function.json の内容

```json:function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "name": "inputBlob",
      "direction": "in",
      "type": "blob",
      "path": "access-counter/{name}",
      "connection": "counters_STORAGE"
    },
    {
      "name": "outputBlob",
      "direction": "out",
      "type": "blob",
      "path": "access-counter/{name}",
      "connection": "counters_STORAGE"
    }
  ]
}
```

### 3.4. Code

```javascript: index.js
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (context.bindings.inputBlob !== undefined) {
        const curCount = parseInt(context.bindings.inputBlob) + 1;
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: curCount
        };
        context.bindings.outputBlob = String(curCount);

        context.log.info("current count: ", curCount);
    }
    else {
        context.res = {
            status: 404,
            body: "Undefined counter name: " + req.query.name
        };
    }
}
```

## 4. 実行画面
・OK なアクセス時の例
`https://*.azurewebsites.net/api/HttpTrigger1?name=counter` へのアクセス時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/7a8415f9-4b9a-ae69-61b2-856d6cef738a.png)

・NG なアクセス時の例
`https://*.azurewebsites.net/api/HttpTrigger1?name=tororo` へのアクセス時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/4e9d39d0-821f-7d60-944e-ac9aa32daedc.png)

# おわりに
Azure Functions ではコードの前後に対する Binding を定義する事で Azure Storage への入出力を実現しているようだ。[参考#3](#参考)のモジュールを利用すれば、Functions のコード上で Azure Storage へ明示的な接続も行えそうにも思えるがまだ未検証である。
また、入力元と同一ファイルへの書き出しを行える事も分かった。但し、今回の実装では、同時アクセスされた場合に適切に増加できずに下記のようになる問題も見つかった。同時に利用されることのない Function である場合のみ同一リソースでの入出力が可能と考えるのが良さそうだ。

```batch: コマンド実行
start curl https://*.azurewebsites.net/api/HttpTrigger1?name=counter
start curl https://*.azurewebsites.net/api/HttpTrigger1?name=counter
start curl https://*.azurewebsites.net/api/HttpTrigger1?name=counter
start curl https://*.azurewebsites.net/api/HttpTrigger1?name=counter
```
↓
```: ログ
2022-05-31T00:11:26.133 [Information] JavaScript HTTP trigger function processed a request.
2022-05-31T00:11:26.134 [Information] current count:  29
2022-05-31T00:11:26.138 [Information] JavaScript HTTP trigger function processed a request.
2022-05-31T00:11:26.138 [Information] current count:  29
2022-05-31T00:11:26.140 [Information] JavaScript HTTP trigger function processed a request.
2022-05-31T00:11:26.140 [Information] current count:  29
2022-05-31T00:11:26.142 [Information] JavaScript HTTP trigger function processed a request.
2022-05-31T00:11:26.142 [Information] current count:  29
```

# 参考

1. 入力バインディングと出力バインディングを使用して Azure Functions を連結する - Learn | Microsoft Docs
   https://docs.microsoft.com/ja-jp/learn/modules/chain-azure-functions-data-using-bindings/?ns-enrollment-type=learningpath&ns-enrollment-id=learn.create-serverless-applications
1. Azure Functions における Azure Blob Storage のトリガーとバインド | Microsoft Docs
   https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob?tabs=in-process%2Cextensionv5%2Cextensionv3&pivots=programming-language-javascript
1. クイックスタート: Azure Blob Storage ライブラリ v12 - Python | Microsoft Docs
   https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-quickstart-blobs-python?tabs=environment-variable-windows