Vantiqから直接Azure CosmosDBのAPIを呼び出してみた

# はじめに
この記事ではVantiqから直接Azure CosmosDBのAPIと連携する方法を紹介します。  
単純にAzure CosmosDBのAPIを試してみたい、といった場合には[Azure CosmosDB APIをたたいてみた](https://qiita.com/watav/private/4874aa1ff8792603e698)をご覧ください。

本記事ではVantiqのProcedureからCosmosDBに対して以下を試してみます。
- ①document(データレコード)の作成
- ②document一覧の取得
- ③クエリーを利用し特定のdocumentを取得  

## 動作条件
Vantiq Server v1.34以降

# 1. CosmosDB の構成
本手順ではCosmos DBアカウントのAPIを **コア(SQL)** で作成した場合の連携方法を紹介します。  
他のAPIを選択して作成した場合は各リファレンスを参照して必要に応じて変更してください。

Cosmos DBアカウントの作成ができたら「コンテナーの追加」からコンテナーを作成します。設定は以下の通りです。
- Database id  
  - 「Create New」を選択し以下を入力
    - `Vantiq`
- Container id  
  - `sensors`
- Partition key
  - `/sensor_id`

OKボタンをクリックしコンテナを作成します。

## URIとkeyの取得

APIを利用するためにURIとkeyを **設定 > キー** から取得します。  
次のVantiq リソース作成で**URI** と **プライマリキー** の値を利用するため値をひかえておきます。

# 2. Vantiq リソースの作成

## Sourceの作成
以下のSourceを作成する。
- Source Name
  - CosmosDB
- Source Type
  - REMOTE
- Server URI
  - 確認したCosmosDBのURI

## Procedureの作成

### API呼び出しに必要なハッシュ値計算Procedureの作成

Azure CosmosDB のAPI呼び出しにはAuthorizationヘッダーにハッシュ値を計算しシグネチャを含めなければいけないため、その値を生成するProcedureを作成しておきます。  
生成方法の詳細は[Access control in the Azure Cosmos DB SQL API - Azure Cosmos DB REST API | Microsoft Docs](https://docs.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources)を参照してください。

```
PROCEDURE getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceId, date, masterKey)

var TYPE = "master"
var VERSION = "1.0"

var key = Decode.base64Raw(masterKey)

var text = ""
text += verb ? (verb.toLowerCase() + "\n") : "\n"
text += resourceType ? (resourceType.toLowerCase() + "\n") : "\n"
text += resourceId ? (resourceId + "\n") : "\n"
text += date ? (date.toLowerCase() + "\n") : "\n"
text += "\n"

var signature = Hash.hmacSha256(key, text)
signature = Encode.base64(signature)
var token = "type=" + TYPE + "&ver=" + VERSION + "&sig=" + signature
token = encodeUriComponent(token)
return token

```

上記`getAuthorizationTokenUsingMasterKey` ProcedureをCosmosDBのdocument作成などの各APIへのリクエストを行うProcedure内で呼び出して利用します。

### ①document作成を行うProcudureの作成

documentの作成(CosmosDBへレコードの登録)を行うProcedureを作成します。  
参考: [Create Document - Azure Cosmos DB REST API | Microsoft Docs](https://docs.microsoft.com/en-us/rest/api/cosmos-db/create-a-document)  

今回の場合はセンサーIDと温度のデータを含んだdocumentの登録を行います。

また、CosmosDBへのdocumentの作成には重複しないIDを指定する必要があります。  
登録する際に必要なIDは`{センサーID}_{UNIX時間}`として生成するように、処理をProcudureに含めています。  

**注意:**  
リファレンスにはidが自動作成される表記がありますが、それはSDKを利用した場合のみであり、**REST APIを直接利用する場合は該当しない模様**  
=> SDKを利用しない場合はidを生成する処理が必要となります。  
参照: [No ID supplied on POST = One of the specified inputs is invalid](https://social.msdn.microsoft.com/Forums/en-US/743f610b-ee53-4461-8d0e-108d72c8dd6c/no-id-supplied-on-post-one-of-the-specified-inputs-is-invalid?forum=azurecosmosdb)

```
PROCEDURE createDocument()

var REQUEST_METHOD = "POST"
var RESOURCE_TYPE = "docs"
var RESOURCE_ID = "dbs/Vantiq/colls/sensors"
var MASTER_KEY = "<INPUT-YOUR-COSMOSDB-PRIMARY-KEY>"

// Test sensor data
var SENSOR_ID = "sensor_1"
var TEMPERATURE = 17.2

var now = now()
var date = format("{0, date,EEE',' dd MMM yyyy HH:mm:ss 'GMT'}",now)

var token = getAuthorizationTokenUsingMasterKey(REQUEST_METHOD, RESOURCE_TYPE, RESOURCE_ID, date, MASTER_KEY)

var body = {
    "id": SENSOR_ID + "_" + toString(date(now, "date", "epochMilliseconds")),
    "sensor_id": SENSOR_ID,
    "temperature": TEMPERATURE
}

var header = {
    "Accept": "application/json",
    "Authorization": token,
    "x-ms-date": date,
    "x-ms-version": "2018-12-31",
    "x-ms-documentdb-partitionkey": "[\"" + SENSOR_ID + "\"]"  
}

var requestPath = "/" + RESOURCE_ID + "/docs"

SELECT * FROM SOURCE CosmosDB WITH method = REQUEST_METHOD, headers = header, body = body, path = requestPath

```

変数`MASTER_KEY`の値には控えておいた`プライマリキー`を設定しProcudureを保存します。
Procedureを実行し、CosmosDBへdocumentの作成が成功すると以下のような結果が返ってきます。

```json
[
   {
      "id": "sensor_1_1668669694733",
      "sensor_id": "sensor_1",
      "temperature": 17.2,
      "_rid": "om52AJTL5HEBAAAAAAAAAA==",
      "_self": "dbs/om52AA==/colls/om52AJTL5HE=/docs/om52AJTL5HEBAAAAAAAAAA==/",
      "_etag": "\"0000930b-0000-0700-0000-6375e0ff0000\"",
      "_attachments": "attachments/",
      "_ts": 1668669695
   }
]
```

Azure PortalのCosmosDBデータエクスプローラからーも作成したsensorsコンテナーのItemsに上記documentが格納されていることを確認できます。

### ②document取得を行うProcudureの作成

documentの一覧を取得するProcedureを作成します。  
参考: [List Documents - Azure Cosmos DB REST API | Microsoft Docs](https://docs.microsoft.com/en-us/rest/api/cosmos-db/list-documents)  

```
PROCEDURE listDocuments()

var REQUEST_METHOD = "GET"
var RESOURCE_TYPE = "docs"
var RESOURCE_ID = "dbs/Vantiq/colls/sensors"
var MASTER_KEY = "<INPUT-YOUR-COSMOSDB-PRIMARY-KEY>"

var now = format("{0, date,EEE',' dd MMM yyyy HH:mm:ss 'GMT'}",now())

var token = getAuthorizationTokenUsingMasterKey(REQUEST_METHOD, RESOURCE_TYPE, RESOURCE_ID, now, MASTER_KEY)

var header = {
    "Accept": "application/json",
    "Authorization": token,
    "x-ms-date": now,
    "x-ms-version": "2018-12-31"
}

var requestPath = "/" + RESOURCE_ID + "/docs"
SELECT * FROM SOURCE CosmosDB WITH method = REQUEST_METHOD, headers = header, path = requestPath

```

変数`MASTER_KEY`の値には控えておいた`プライマリキー`を設定しProcudureを保存します。
Procedureを実行すると以下のように作成したdocumentの一覧を取得できます。

```json
[
   {
      "_rid": "om52AJTL5HE=",
      "Documents": [
         {
            "id": "sensor_1_1668669694733",
            "sensor_id": "sensor_1",
            "temperature": 17.2,
            "_rid": "om52AJTL5HEBAAAAAAAAAA==",
            "_self": "dbs/om52AA==/colls/om52AJTL5HE=/docs/om52AJTL5HEBAAAAAAAAAA==/",
            "_etag": "\"0000930b-0000-0700-0000-6375e0ff0000\"",
            "_attachments": "attachments/",
            "_ts": 1668669695
         }
      ],
      "_count": 1
   }
]
```


### ③クエリを利用して特定のdocumentを取得するProcudureの作成

クエリを利用したdocumentの取得を行うProcedureを作成します。  
今回の場合はsensor_idに対しての条件のクエリを送信します。  

参考: [Query Documents - Azure Cosmos DB REST API | Microsoft Docs](https://docs.microsoft.com/en-us/rest/api/cosmos-db/query-documents)

```
PROCEDURE queryDocument()

var REQUEST_METHOD = "POST"
var RESOURCE_TYPE = "docs"
var RESOURCE_ID = "dbs/Vantiq/colls/sensors"
var MASTER_KEY = "<INPUT-YOUR-COSMOSDB-PRIMARY-KEY>"

var QUERY_BODY = {
    "query": "SELECT * FROM Items c WHERE c.sensor_id = \"sensor_1\"",
    "parameters": []
}

var now = format("{0, date,EEE',' dd MMM yyyy HH:mm:ss 'GMT'}",now())

var token = getAuthorizationTokenUsingMasterKey(REQUEST_METHOD, RESOURCE_TYPE, RESOURCE_ID, now, MASTER_KEY)

var header = {
    "Accept": "application/json",
    "Authorization": token,
    "x-ms-date": now,
    "x-ms-version": "2018-12-31",
    "x-ms-documentdb-isquery": "true" ,
    "x-ms-documentdb-query-enablecrosspartition": "true"
}

var requestPath = "/" + RESOURCE_ID + "/docs"

SELECT * FROM SOURCE CosmosDB WITH method = REQUEST_METHOD, headers = header, body = stringify(QUERY_BODY), path = requestPath, contentType = "application/query+json"

```

変数`MASTER_KEY`の値には控えておいた`プライマリキー`を設定しProcudureを保存します。
Procedureを実行すると以下のようなにクエリ(sensor_idの値がsensor_1)に該当するdocumentを取得できます。  

```json
[
   {
      "_rid": "om52AJTL5HE=",
      "Documents": [
         {
            "id": "sensor_1_1668669694733",
            "sensor_id": "sensor_1",
            "temperature": 17.2,
            "_rid": "om52AJTL5HEBAAAAAAAAAA==",
            "_self": "dbs/om52AA==/colls/om52AJTL5HE=/docs/om52AJTL5HEBAAAAAAAAAA==/",
            "_etag": "\"0000930b-0000-0700-0000-6375e0ff0000\"",
            "_attachments": "attachments/",
            "_ts": 1668669695
         }
      ],
      "_count": 1
   }
]
```

続いてクエリを変更し、sensor_idがsensor_2のdocumentの取得を行ってみます。  
Procedure内の変数`QUERY_BODY`の`query`フィールド内のsensor_1をsensor_2に変更し再度実行してみます。  
sensor_idがsensor_2のdocumentは登録していないので、以下のような結果が返ってきます。

```json
[
   {
      "_rid": "om52AJTL5HE=",
      "Documents": [],
      "_count": 0
   }
]
```

**補足:**    
Vantiqはapplication/query+jsonのContent-Typeに対応していないです。(2022/11現在）  
そのため、上記のProcedureでは以下を行っています。
- SELECT文のcontentTypeパラメータで"**application/query+json**"を渡す
- application/query+jsonの場合、**bodyをJSONと認識しないので、stringify()でString化**する


以上がVantiqからAzure CosmosDBのREST APIを直接利用する際のサンプルです。