AzureCosmosDBでUDFを使ってjson arrayのPagenationとSort

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

# 経緯
[前回の投稿](http://qiita.com/tstakano-yj/items/120be99b3bba6c8d645e)で、Eventhubs経由で登録することができたので、あとは検索機能を作らないとということで、SortとPagenationのついたsearchAPIをAzureFunctionsで実装してみた。

# やりたいこと
下記のようなデータがCosmosDBに保存されている

```json:ID-user1
{
  "Contents": [
    {
      "ContentsId": "c0001",
      "Time": 1500532318
    },
    {
      "ContentsId": "c0006",
      "Time": 1500535647
    },
    {
      "ContentsId": "c0002",
      "Time": 1500538621
    }
  ],
  "Pkey": "reserve",
  "id": "user1"
}
```

このContents内をTime（unix timestamp）順にSortしたり、Pagenation（start, end）で絞ったりしたい。
[AzureForum](https://feedback.azure.com/forums/263030-documentdb/suggestions/6350987--documentdb-allow-paging-skip-take)を見る限り、現時点ではTOPのサポートしかない模様・・・。これだとページネーションできない。
しかも今回は1ドキュメントのArrayになっているnestedオブジェクトのSortやPagenationなので、そもそも用途が違う気がする。。。


AzureCosmosDBの[UDF（user defined functions）](https://docs.microsoft.com/en-us/azure/cosmos-db/programming)を利用して作ってみることにした。
udfで作成して、functionsで設定するSQLに埋め込むような形で行こうかと。

# 実装
## UDF
まずはUDFの作成。
<img width="559" alt="スクリーンショット 2017-07-25 15.20.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/2e0153b6-8ff3-bccf-3977-6cfcff928b2c.png">

```js:sort
function sort(contents, start, end, sort) { 
  function compareTime(a, b) {
    if (sort == 'DESC') {
      return b.Time - a.Time;
    }
    else{
      return a.Time - b.Time;
    }
  }
  return contents.sort(compareTime).slice(start, end);
}
```

## SQL
SQLには下記のような形で埋め込み、更にFunctionsの統合のsqlに設定する。
udfを使用する際はudf.[ID]でアクセスできる。

```sql:SQL
SELECT c.id, c.Pkey, udf.sort(c.Contents, {start}, {end}, {order}) as Contents FROM c where c.id = {docId}
```

## Functions
下記の通り。

統合はこんな感じ
<img width="1140" alt="スクリーンショット 2017-07-25 15.22.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/bfb3ad35-7379-6f44-f0c1-2a6ecc12a4b9.png">


http-inputのルートテンプレートで必要な引数をもらう設定
<img width="434" alt="スクリーンショット 2017-07-25 15.24.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/8ec2f4a4-aa34-31a5-12af-e73ec8a321db.png">

そのまま引数に設定する。
気をつけるポイントは、inDocumentsをIEnumerable<dynamic> 型にすること。
SQLで取得する場合はID指定であっても複数ドキュメント限定らしい。

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

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

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

public static HttpResponseMessage Run(Input input, int start, int end, string order,
    HttpRequestMessage req, IEnumerable<dynamic> inputDocuments, TraceWriter log)
{
    var inputDocument = inputDocuments.First();
    log.Info($"invoked. start:{start}. limit:{end}. order:{order}");
    if (inputDocument != null)
    {
　　　　　　　　　　　　　　　　var responseContent = JsonConvert.SerializeObject(inputDocument);
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Content = new StringContent(responseContent, Encoding.UTF8, "application/json");
        return response;
    }
    else
    {
        //not found. do nothing.
        return req.CreateResponse(HttpStatusCode.NotFound);
    }
}
```
以上。

# Test
テストフォームが勝手に適用される。便利。。。
<img width="382" alt="スクリーンショット 2017-07-25 15.28.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/c07f5987-664b-bf3b-2a07-3bb1c615df85.png">


取得完了。さくっと行けた。startとendだけだと利便性が悪いかもなので、limitとoffsetにしたほうがいいかも。

とりあえずプロトタイプはできた。

# 所感
パフォーマンス面に懸念は残るが、現時点ではこうするか、Functions内C#でSortの二択になりそうな気がする。
チーム内で相談した上で、今後ストレステスト等を経て決断していきたいと思う。