AzureEventGridを触ってみた

# はじめに

仕事でIoTHub, EventHubs, ServiceBusと触ってきましたが、それぞれ利用用途が異なります。AWSもそうですがたくさんのキューサービスが存在します。
今回はEventGridを触っていきたいと思います。

# EventGridとはなにか？

このサービスはEventメッセージを扱うサービスで、送る側は後にどんな処理が行われるかについては関知しません。このへんはIoTHub,EventHubsと同じですね。
逆にメッセージを扱うサービスはServiceBusです。受け取った側が最後まで責任をもってキューからデータを削除してあげる必要があります。

ServiceBusについては先日まとめたエントリーをご参照ください。

* [Qiita - AzureServiceBusを色々触ってみた](https://qiita.com/uzresk/items/582a7a92e85677fb8365)

**イベントソース**は[こちら](https://docs.microsoft.com/ja-jp/azure/event-grid/event-sources)にある通りたくさんのサービスに対応しています。

* Container Registry
* EventHubs
* IoTHubs
* Key Vault
* MediaService
* ServiceBus
* ストレージ
* AppConfiguration
* SignalR
* MachineLearning

**トピック**というのはイベントのエンドポイントのことです。イベントのソースはトピックに対してイベントを送信します。
**サブスクリプション**には、どのトピックから受信してどのイベントハンドラ起動させるかを設定しておきます。

**イベントハンドラ**はイベントの送信先です。[こちら](https://docs.microsoft.com/ja-jp/azure/event-grid/event-handlers)にある通り色々なサービスに対応しています

* AzureAutomation
* AzureFunctions
* EventHubs
* LogicApps
* ServiceBus
* QueueStorage
* WebHook

AzureFunctionsにつなげば超強力なBinding機能でCosmosにもつなげますし使いどころはたくさんありそうです。

# EventGridドメインを作る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/2114daaf-4233-b36e-389b-3d2daeaa6a52.png)

イベントスキーマに何を設定すればよいのか悩むと思いますが、「クラウドイベントスキーマ v1.0」というものがあります。
これはCNCFが策定するクラウドイベントの標準仕様でAWSやOracle,MSが策定に参画しており、イベントのクラウド間相互運用性や互換性が高まることを狙っているものです。

現在ではFunctionがネイティブサポートされていないため、CloudEventsではなく、「イベントグリッドスキーマ」を使っていきます。

# Functionの作成

イベントサブスクリプションを定義していきたいところですが、先にFunctionを作っておかないといけないので作っておきます。

いつも通り[Azure Functions Core Tools](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux)で作っていきます。

```
func new
```

した後に「JavaScript」→「6. Azure Event Grid trigger」を選択して関数のひな型を作りAzureFunctionsをデプロイしておきます。

このへんの操作は詳しくは下記のエントリを参照ください

* [[初学者向け]AzureFunctions(TimerTrigger)＋nodejsをはじめてみよう](https://qiita.com/uzresk/items/c3b312c5df16026bbcc2)

今回は、1つのイベントソースで2つのFunctionを起動させてみたいと思いますので2つのFunctionをデプロイしました。

それぞれソースはこんな感じです

```azure-eventgrid-samples/index.js
module.exports = async function (context, eventGridEvent) {
    context.log("#1")
    context.log(eventGridEvent);
};
```

```azure-eventgrid-samples-2/index.js
module.exports = async function (context, eventGridEvent) {
    context.log("#2")
    context.log(eventGridEvent);
};
```

# トピックと紐づける

それではサブスクリプションを登録していきましょう。
特に難しいところはないはずです。ドメイントピックの名前は今回はtopicという名前にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/bff37aeb-12ba-b9b7-6270-db5808a69464.png)

同じトピックに2つのサブスクリプションを紐付けましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/1bcc72de-91ac-d465-30a4-debf4f0c2bac.png)

# イベントを送る

[Azure Event Grid libraries for JavaScript　Example code](https://docs.microsoft.com/ja-jp/javascript/api/overview/azure/eventgrid?view=azure-node-latest)のままだとエラーが出てしまいます。

eventsには、eventTimeとtopicが必要なので忘れずに設定しましょう。


```index.js
const EventGridClient = require("azure-eventgrid");
const msRestAzure = require("ms-rest-azure");
const uuid = require("uuid").v4;
const moment = require("moment");

const topicCred = new msRestAzure.TopicCredentials(process.env.TOPIC_KEY);
const EGClient = new EventGridClient(topicCred);
const topicEndpoint = process.env.TOPIC_ENDPOINT;

const events = [
    {
        id: uuid(),
        subject: 'TestSubject',
        topic: 'topic',
        dataVersion: '1.0',
        eventType: 'Microsoft.MockPublisher.TestEvent',
        eventTime: new Date().toISOString(),
        data: {
            name: 'uzresk',
            message: 'hello!'
        }
    }
];

return EGClient.publishEvents(topicEndpoint, events).then((result) => {
    return Promise.resolve(console.log('publish successfully.'))
}).catch((err) =>{
    console.log("ERROR-------------------------");
    console.log(err);
});
```

TOPIC_ENDPOINTですが、ドメインの画面にある「ドメインエンドポイント」をそのまま設定してはいけません。

`https://xxxxxxx.japaneast-1.eventgrid.azure.net/api/events`

ではなく↓を設定します。

`xxxxxxx.japaneast-1.eventgrid.azure.net`

では実行してみます。

```shell
$ node index.js
publish successfully.
```

少し経つと、EventGridドメインにある画面で1 Published Event, 2 Matched Eventとなっていれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/30c12960-dd8f-5ac2-6f79-e6cc1df9711b.png)

Functionsのログも確認しておきましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/a315c940-1ca8-a4c4-1536-2323f41ad30c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/4bf815c8-53f3-ec4f-8e1b-d3a329f398b8.png)

# Dead Letter

イベントハンドラに渡せなかったりした場合はDeadLetterにデータが渡ります。
ServiceBusの場合はDeadLetterQueueというキューにデータが渡りましたがEventGridの場合はストレージに入るようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/c267b600-2fdd-33cf-c631-5e96aeabef36.png)

イベントの最大試行回数分動いてからDeadLetterにデータが渡るため実験する場合は短めに設定しましょう。

対象の関数を停止しておきわざと失敗させます。
配信不能だったデータは以下のパスに保管されます。(日付はUTCです)

`ストレージアカウント/トピック名/サブスクリプション名/年/月/日/xxxxxx.json`

```
[
  {
    "id": "xxxxxxxxxx-3b29-4bfc-a634-xxxxxxxxxx",
    "eventTime": "2020-02-26T23:22:02.0000000Z",
    "eventType": "Microsoft.MockPublisher.TestEvent",
    "dataVersion": "1.0",
    "metadataVersion": "1",
    "topic": "/subscriptions/xxxxxxxxxx/resourceGroups/rg-xxxxxxxx/providers/Microsoft.EventGrid/domains/evg-tisesk/topics/topic",
    "subject": "TestSubject",
    "deadLetterReason": "MaxDeliveryAttemptsExceeded",
    "deliveryAttempts": 2,
    "lastDeliveryOutcome": "Forbidden",
    "publishTime": "2020-02-26T23:22:03.8286689Z",
    "lastDeliveryAttemptTime": "2020-02-26T23:22:04.1762595Z",
    "data": {
      "name": "uzresk",
      "message": "hello!"
    }
  }
]
```

DeadLetterとなったデータはBlobStorage Trigger Functionを使って再試行や通知を行うことで対応ができるかと思います。

.Netのサンプルは[「.NETのAzure Event Grid Dead Letterサンプル」](https://docs.microsoft.com/ja-jp/samples/azure-samples/event-grid-dotnet-handle-deadlettered-events/event-grid-dotnet-handle-deadlettered-events/)にありますが、普通のTrigger Functionsと同じです。

DeadLetterに入る時点で時間が経っているのであまり気にする状況はないかと思いますが、BlobTriggerは最大10分間遅延することは忘れないでおきましょう。

>関数アプリを従量課金プランで使用しているときに、関数アプリがアイドル状態になっている場合、新しい BLOB の処理が最大で 10 分間遅延する可能性があります。 この待機時間を避けるには、Always On が有効な App Service プランに切り替えることができます。 BLOB ストレージ アカウントで Event Grid トリガーを使用することもできます。 

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=csharp#event-grid-trigger

# ServiceBusとつないでみる

同じようにサブスクリプションを作って、イベントハンドラをServiceBusにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/fc9e850b-35ec-5807-cfc0-586996aa348a.png)

ServiceBusExplorerで確認してみると確かにデータが入っていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/4b5a728c-f697-3424-1918-307148a284e8.png)

# EventGridの後ろにServiceBusを挟むのはなぜか？

https://docs.microsoft.com/ja-jp/azure/event-grid/delivery-and-retry

```
Event Grid は、持続性のある配信を提供します。 各サブスクリプションに対して、最低 1 回は各メッセージを配信します。 イベントは、直ちに各サブスクリプションの登録済みのエンドポイントに送信されます。 エンドポイントがイベントの受信を確認しない場合、Event Grid はイベントの配信を再試行します。
```

ここに記載の通り最低1回、2回送信されることもありうる訳です。

EventGridのバックエンドの処理が冪等にできない場合や、1回しか処理できない場合はEventGridのバックエンドにServiceBusを配置し、ServiceBusのキューの設定で重複排除しておくことで1回しか処理されないようなメッセージを配信することが可能になるわけです。