AzureFunctionsでCosmosDBのクエリ検索restサービスと監視

GYAOのtsです。
我々のチームは、オールパブリッククラウドで、[Microservice Architecture](http://microservices.io/patterns/microservices.html)を採用した次期バックエンドを設計中です。

# 経緯
チームが変わったりやなんやらで[前回の投稿](http://qiita.com/tstakano-yj/items/a575913c1beb5d315737)　から結構時間が空いてしまった。
引き続き我々のチームは戦陣切ってクラウド化を推進していこうと思う。
（なんでもクラウドにすればいいという意味ではなく、自由度を高め、視野を広げ、普通の力をつけようという意味で）

今回はfunctionを軸に構築する。
全ての主要クラウドベンダーから出ているサーバーレスアーキテクチャという代物だが、流行ってますね。
先日Azureの方とお話しましたが、pushされていらっしゃいました。Azureとしても力を入れていく領域なのかなと個人的には感じている。
というのも、使い始めた頃よりだいぶ進化していて、認証やらswaggerドキュメントの生成やら細かい便利そうな機能が追加されていてmicro serviceのIFを作ってくださいと言わんばかりな形になってきているので、我々もそれに乗っかろうかなと。「LogicAppsでもFunctionでも実装できない複雑なビジネスロジックがあった場合のみコンテナでrestを作ってそれをFunctionsやLogicAppsから呼ぶ」みたいなスタンスでいくとシンプルかなと。

# 作ってみる
## 実現したいこと
今回はCosmosDBに以下の形で入った複数のドキュメントをクエリ検索するrestサービスを作成する。

```json:riceさんが複数のオブジェクト（コンテンツ）を持っている
{
  "contents": [
    {
      "id": "A00001",
      "time": 1497845466
    },
    {
      "id": "C00001",
      "time": 1497845586
    },
    {
      "id": "D00001",
      "time": 1497845686
    }
  ],
  "id": "rice",
  "pkey": "4"
}
```
```json:okawariさんが別の複数のオブジェクト（コンテンツ）を持っている
{
  "contents": [
    {
      "id": "A00001",
      "time": 1497661986
    },
    {
      "id": "B00001",
      "time": 1497663986
    },
    {
      "id": "F00001",
      "time": 1497671986
    }
  ],
  "id": "okawari",
  "pkey": "4"
}
```
上記をidで検索して、「該当のコンテンツを保有しているのは誰か」を検索する。
クエリは下記の通り

```sql: CosmosDBクエリ
SELECT d.id FROM docs d JOIN c IN d.contents WHERE c.id in({contentsId})
```
ちなみにクエリの構築は[こちら](https://www.documentdb.com/sql/demo)を使うと便利です。

## AzureFunctionsの構築
様々実装方法はあると思うが、今回はC#で構築。
まずはリソースの作成。
<img width="327" alt="スクリーンショット 2017-06-29 13.01.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/2c908692-183b-f173-7dec-003016e67de3.png">
気をつけるのは、ApplicationInsightsをonにすること（後々監視でlogを拾えるようにするため）。

次は関数の作成だが、テンプレートでC#のHttp triggerがあると思うので、それを使用する。
関数名は下記のようにしてみた。まずは基本的な挙動の設定をしないといけないので、「統合」画面に行く。
<img width="239" alt="スクリーンショット 2017-06-29 13.42.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/874e5975-2048-f4e8-9305-e43e99fa42e7.png">

AzureFunctionsはトリガ、入力、出力の基本項目をまず設定する。その基本項目の設定でコードとのバインドを作成し、それが関数の構築画面で使えるという流れ。
入力はトリガ以外の入力があれば設定（今回の場合はCosmosDBから検索結果の入力があるので設定）する。

今回のトリガ、入力、出力は下記の通り。
<img width="1099" alt="スクリーンショット 2017-06-29 13.41.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/6acd509c-c275-8c9e-c540-88346aed4ce4.png">
各項目を説明していく。
### トリガ（HTTP）
<img width="994" alt="スクリーンショット 2017-06-29 14.20.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/6062ac99-2268-d79a-0a65-843e55c6d8c0.png">

オフィシャルドキュメントは[こちら](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook)

* この関数はGETのみで使用するために、HTTPメソッドはGETに絞っておく。
* 認証をFunctionレベルでかけたいので、認証レベルはFunction。
* **要求inputパラメータ名が関数の作成画面で使用する同名のパラメータとバインドされる。**
* **ルートテンプレートで、先程のcontentsIdのパラメータを指定できる。そうすると、関数の引数で受け取れる。 **

### 入力（Azure DocumentDB）
CosmosDBに名前が変わったはずだけど、ここはまだみたい。
<img width="908" alt="スクリーンショット 2017-06-29 14.46.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/d9b769f5-9f0b-1c0f-cd18-6eedd7bd69f0.png">
オフィシャルドキュメントは[こちら](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-documentdb)

* ドキュメントパラメータ名が、関数画面の同名変数にバインドされます。
* SQLクエリに下記のように記載

```sql: CosmosDBクエリ
SELECT d.id FROM docs d JOIN c IN d.contents WHERE c.id in({contentsId})
```

### 出力（HTTP）
<img width="362" alt="スクリーンショット 2017-06-29 15.10.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/a8201172-dcb6-67ab-f860-5631a2ce4c29.png">

関数の戻り値をそのまま使用する。


以上で基本設定は終わり。次に関数の構築画面にいって、ここで定義したバインドを使用して関数を作っていく。
<img width="242" alt="スクリーンショット 2017-06-29 15.13.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/fa14cc24-6226-9e40-9a85-0a5def2a7c47.png">

関数は下記の通り。超簡素。

```csharp:lookupByContentsId
#r "Newtonsoft.Json"

using System;
using System.Net;
using Newtonsoft.Json;

public class Input
{
    public string contentsId { get; set; }
}

public static HttpResponseMessage Run(Input input, HttpRequestMessage req, IEnumerable<dynamic> inputDocument, TraceWriter log)
{
    log.Info("invoked.");
    if (inputDocument.Count() > 0)
    {
        var response = JsonConvert.SerializeObject(inputDocument);
        log.Info($"Document: {response}");
        return req.CreateResponse(HttpStatusCode.OK, $"{response}");
    }
    else
    {
        return req.CreateResponse(HttpStatusCode.NotFound);
    }
}
```
引数をみてみると、先程定義した名前と同名で定義してあると思う。各引数に関して説明をしていく。

* input
    * トリガのHttpをバインドしたバインド先。通常はHttpRequestMessageオブジェクトにバインドするが、今回は、リクエストの中にcontentsIdが入っていて、それを直にCosmosDBへのクエリのパラメータとして使用するため、こういう形となった。詳細は[オフィシャル](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-documentdb)を参照。
    * 関数の外でInputクラス（POCO）をこさえると、そこにパラメータがバインドされる。CosmosDBを「入力」に使用する場合はこうする模様。Queueのpayloadのjsonをバインドするのがオフィシャルの例に乗っているが、今回はトリガのルートテンプレートで設定したcontentsIdをバインドした。（できた。）
* req
    * HttpRequestMessageオブジェクトは上述のように、通常Httpトリガのパラメータバインド先。今回はバインドしないので、コンパイルは通るが、実行時にエラーになる（AzureFunctionsはバインドのないパラメータを引数に取ると実行時エラーとなる）はずだが、ならない。そりゃHttpリクエストだからな。。。特別なんでしょう。ちなみに、レスポンス（HttpResponseMessage）の生成器にもなっているのでないと困る。
* inputDocument
    * CosmosDBの検索結果が格納される。つまり、この関数が呼ばれる前に前述のPOCOを使用したCosmosDBの検索が行われている形となる。
    * 今回はクエリを使用して複数ドキュメントを取得するので、typeはIEnumerable\<dynamic\>
    * ドキュメントが複数返ってくるのでJson.NETを使用して、jsonオブジェクト（リスト）を取得する。
* log
    * Logger。これを使って出力するものを、あとでApplicationInsightsで拾えるように設定します。

## test
実行してみる。
<img width="381" alt="スクリーンショット 2017-06-29 16.03.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/7dc62ea0-968c-52d4-243e-2e9d4368a475.png">
おかわりさんが引っかかった。

## Swaggerドキュメント書いてみる
API Definition（現在はpreview）からGenerate API Definition Templateを押すと、
<img width="1051" alt="スクリーンショット 2017-06-29 16.14.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/4302ac9d-df84-fcfe-48a4-ab9dec9de825.png">

contentsIdとかも含め情報を読み取って雛形を作成してくれる。なんて便利。
プロトタイプを作成して、同時にドキュメント作成、ある程度固まったらステークホルダにドキュメント渡す
みたいなスムーズなコミュニケーションがとれる。

## Logの監視
ApplicationInsightsを使用して、logを一元管理していきたいと思う。
最初にApplicationInsightsをonにしたので、あとは下記を行って、紐付けていく。

1. ApplicationInsightsのプロパティ、のインストルメンテーションキーを取得する。
<img width="597" alt="スクリーンショット 2017-06-29 16.53.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/38b8d467-53b7-4825-8360-aafc5a9dd8f1.png">
1. Functionsのアプリケーション設定でAPPINSIGHTS_INSTRUMENTATIONKEYというkeyで上記で取得した鍵を入力する。
<img width="496" alt="スクリーンショット 2017-06-29 16.58.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/7fa16a27-2dd1-df29-28e3-fe6a3f330a59.png">
<img width="556" alt="スクリーンショット 2017-06-29 17.06.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/f32280a9-5807-42f5-56d3-4e27d14b6189.png">
1. ApplicationInsightsでログの状況を閲覧することができる。
<img width="826" alt="スクリーンショット 2017-06-29 17.07.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/aa790552-66d8-7cf5-6d3d-862c713a08b5.png">

# 所感
日に日にパワーアップするAzureFunctionsをいじってて普通に楽しい。エンジニアがクリエイティブな部分に集中できるように痒いところに手が届いてきた気がします。ドキュメントが若干読みにくいのと、リソース横断的なusageドキュメントが少ない印象のAzureですが、紐解いていくこと自体も楽しいのでよしとします。

今回の体験を経て、全体的にこんな感じにしようと思いました。チームメンバーの了承を得たらこれで行きます。
![zeoliteLatestDiagram (1).png](https://qiita-image-store.s3.amazonaws.com/0/114329/1c21fffc-d80b-f66f-e5dd-c4a8311943b3.png)

# 次回
Eventhubに関して書いていこうと思います。