Azure Cosmos で無料で作るハイスコアDB(2) : APIとデータモデルとAzureテーブル

# はじめに

Azure Cosmos DBはただのNoSQL DBかと思い、[前回の記事](https://qiita.com/kaku3/items/c64eb4acd97ab8e41592)で で無料でハイスコアDBを作り始めたが、Table Api を使わないと期待する実装ができなそうに思え Table Api を調べることにした。

結論を先に述べておくと、Table Api には **ソート機能** がないためハイスコアDBとしては使えなかった。

# APIとデータモデル

Azure Cosmos DB では、DBアカウント作成時にAPIとデータモデルの種類が選択できる。

|種類   |内容   |
|---|---|
|コア(SQL)   |jsonで保存したデータを、SQLの様な文法でqueryしアクセスする   |
|MongoDB 用 Azure Cosmos DB API|[Azure Cosmos DB の MongoDB 用 API](https://docs.microsoft.com/ja-jp/azure/cosmos-db/mongodb-introduction)|
|Cassandra|[Azure Cosmos DB の Cassandra API の概要](https://docs.microsoft.com/ja-jp/azure/cosmos-db/cassandra-introduction)|
|Azure テーブル |[Azure Cosmos DB の概要:テーブル API](https://docs.microsoft.com/ja-jp/azure/cosmos-db/table-introduction) 
|Gremlin(グラフ)|[Azure Cosmos DB での Gremlin API の概要](https://docs.microsoft.com/ja-jp/azure/cosmos-db/graph-introduction)

MongoDB, Cassandra は、同じくNoSQL DB。元々利用していれば同じインターフェース(API)で使えて移行が容易とのこと。どちらも利用していなかったので詳細は省略。
Gremlinは、グラフデータベース。今回の要件に対しては意識が高すぎるので詳細は省略。

コア(SQL)で作成したDBをAzure Portal のコンソールから確認すると生のjsonが表示され「何か違うな」と思いAzure テーブルにしたが、そこは気にせずにコア(SQL)にしておけばよかったが後の祭り。

# Azureテーブル

PartitionKey, RowKey を持つ、Key-Value Store。
Azure Portal のコンソールから確認すると、各項目をヘッダに持つテーブルが表示され「俺の知ってるDBってこれだよな」という安心感があった。また、Apiもシンプルで学習コストも低く見えたので作業を進めた。

![table-api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/7784f750-760d-d611-1e2b-37d2323678a4.png)

※データエクスプローラ(AZURE TABLE API)：よく見るDBっぽい何かで安心感がある画面

create, insert, update と順調に作業を進めたが、read する際に sort ができないことに気づいた。
よって本プロジェクトではAzureテーブルは利用しない事としたが、せっかく調べたので記録として残しておくことにした。

``` test.js
const storage = require('azure-storage')
const client = storage.createTableService('接続文字列')
const eg = storage.TableUtilities.entityGenerator

//
//
// テーブル作成(テーブルのスキーマ定義は行わない)
//
//
client.createTableIfNotExists('<テーブル名>', (error, result) => {
  // 処理が終了するとコールバックが呼ばれる。
})

//
//
// レコードの登録/更新。
//
//

// 登録時に型指定も行う。
const entry = {
PartitionKey: eg.String('<PartitionKeyの値>'),
RowKey: eg.String('<RowKeyの値>'),
no: eg.Int64(1) // 任意の項目
}
client.insertOrReplaceEntity('<テーブル名>', entry, (error, result) => {
  // 処理が終了するとコールバックが呼ばれる。
})

//
//
// 任意のレコードを取得
//
//
client.retrieveEntity('<PartitionKeyの値>', '<RowKeyの値>', key, (error, result) => {
  // result.entry に値が入っている
  // 値は<項目名>._ に入っている
  // console.log(result.entry.no._)   // 1
})

//
//
// TableQuery を利用して、任意のPartition のレコードを取得
//
//

// 上位10件スコア取得、ではなく登録順に10件取得
const q = new storage.TableQuery().select('name', 'score').top(10)

client.queryEntities('scores', q, null, (error, result) => {
  if(error) {
    console.error(error)      
  } else {
    const entries = result.entries
    for(const e of entries) {
      console.log(e.name._, e.score._)
    }
  }
})


```

TableQueryが提供しているインターフェイス(2020/10/10時点)

``` azure-storage.d.ts

// コメント除去して抜粋

export interface TableQuery {
  select(...args: string[]): TableQuery;
  select(args: string[]): TableQuery;

  top(top: number): TableQuery;

  where(condition: string, ...args: any[]): TableQuery;
  and(condition: string, ...args: any[]): TableQuery;
  or(condition: string, ...args: any[]): TableQuery;

  toQueryObject(): Object;
}

export var TableQuery: {
  new(): TableQuery;

  int32Filter(propertyName: string, operation: string, value: string | number): string;
  int64Filter(propertyName: string, operation: string, value: string | number): string;
  doubleFilter(propertyName: string, operation: string, value: string | number): string;
  booleanFilter(propertyName: string, operation: string, value: boolean | string): string;
  dateFilter(propertyName: string, operation: string, value: Date | string): string;
  guidFilter(propertyName: string, operation: string, value: string | any): string;
  binaryFilter(propertyName: string, operation: string, value: Buffer | string): string;
  stringFilter(propertyName: string, operation: string, value: string): string;
  combineFilters(filterA: string, operatorString: string, filterB: string): string;
};
```

# おわりに

NoSQLは、RDBよりチョロい上に無料という思い込みで作業を開始したが、CosmosDBが提供するサービスは想像以上に高機能だった。
MongoDBやCassandraを利用していたのであれば低い学習コストで移行できるかもしれない。

`distinct`と`order by` ができるようでしたらご指摘頂けますと助かります。

※作業開始前にドキュメントはよく読もう。