2020年から始めるAzure Cosmos DB - JavaScript SDK (SQL API)を見てみる (Part.1)

![th.jpeg](https://www.bing.com/th?id=OIP.yovZnkelJ4W3sSoRk-oQtwHaD4&pid=Api&rs=1)

# この記事について

本記事は、2020年3月6日 (米国時間) にて、Azure Cosmos DB に新しく Free Tier (無償利用枠) が登場したことに伴い、改めて Azure Cosmos DB を色々と触っていく試みの 3 回目です。
今回は、[前回記事][PrevLink1] にて作成した CRUD アプリ内で使用している **Microsoft Azure Cosmos JavaScript SDK** について見ていきたいと思います。

- [Microsoft Azure Cosmos JavaScript SDK][npm]
- [Azure/azure-cosmos-js][GitHub1] \(old\)
- [Azure/azure-sdk-for-js][GitHub2]


[PrevLink1]:https://qiita.com/ymasaoka/items/0b0b726354c3cfd38021
[npm]:https://www.npmjs.com/package/@azure/cosmos
[GitHub1]:https://github.com/Azure/azure-cosmos-js/tree/master
[GitHub2]:https://github.com/Azure/azure-sdk-for-js


# 対象読者

- Azure Cosmos DB について学習したい方
- Node.js で Azure Cosmos DB への CRUD 操作を行いたい方
- Microsoft Azure Cosmos JavaScript SDK の動作について理解したい方


# Microsoft Azure Cosmos JavaScript SDK

実際に、[Microsoft Docs][MSDocs] の内容を元に、JavaScript SDK (SQL API) の中身を見ていきます。
今回は Azure Cosmos DB に接続する際に生成する、CosmosClient について確認します。

## CosmosClient

TypeDoc の記載は、以下の通りです。

>Provides a client-side logical representation of the Azure Cosmos DB database account.
>This client is used to configure and execute requests in the Azure Cosmos DB database service.
>Azure Cosmos DBデータベースアカウントのクライアント側の論理表現を提供します。
>このクライアントは、Azure Cosmos DBデータベースサービスで要求を構成および実行するために使用されます。


```typescript

const client: CosmosClient = new CosmosClient({ endpoint, key });
```

上にある通り、**endpoint** と **key** を使用してインスタンスを作成しています。

- endpoint: Azure Cosmos アカウント URI、
- key: Azure Cosmos アカウントのプライマリキー or セカンダリキー

どのコンストラクタが動いているのかを確認すると

```typescript
(alias) new CosmosClient(options: CosmosClientOptions): CosmosClient (+1 overload)
import CosmosClient
```

とあり、`CosmosClient(options: CosmosClientOptions)`が動いているようです。
中身を確認してみます。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/CosmosClient.ts
/**
 * Creates a new {@link CosmosClient} object. See {@link CosmosClientOptions} for more details on what options you can use.
 * @param options bag of options - require at least endpoint and auth to be configured
 */
constructor(options: CosmosClientOptions); // tslint:disable-line:unified-signatures
```

あれ、1行しかない？と思ったらすぐその下に`constructor(optionsOrConnectionString: string | CosmosClientOptions)`があったので焦りました。。
このコンストラクタは長いので、部分ごとに見ていきます。

### コンストラクタ処理 (1)

```typescript:
if (typeof optionsOrConnectionString === "string") {
  optionsOrConnectionString = parseConnectionString(optionsOrConnectionString);
}
```

これは単純に、コンストラクタの引数が`string`か`CosmosClientOptions`かを判別しています。
引数が string の場合は、if 文の中で接続文字列を使って CosmosClientOptions を生成し、 optionsOrConnectionString に代入しています。
コンストラクタの引数が string と CosmosClientOptions と異なっているので、お決まりな感じの処理です。
*コンストラクタの引数の型が違うからと言い、内容がほとんど重複するようなコンストラクタをしっかり分けて書く人をたまに見かけます。このコードは「そんな無駄なことしなくていいよ」と教えてくれるいい例ですね。*

### コンストラクタ処理 (2)

```typescript:
optionsOrConnectionString.connectionPolicy = Object.assign(
  {},
  defaultConnectionPolicy,
  optionsOrConnectionString.connectionPolicy
);
```

ここでは、CosmosClientOptions の `connectionPolicy`に値を設定しています。
その前に、**CosmosClientOptions って何者だ?**、という疑問があるので、先にこちらを見てみます。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/CosmosClientOptions.ts
export interface CosmosClientOptions {
  endpoint: string;
  key?: string;
  resourceTokens?: { [resourcePath: string]: string };
  tokenProvider?: TokenProvider;
  permissionFeed?: PermissionDefinition[];
  connectionPolicy?: ConnectionPolicy;
  consistencyLevel?: keyof typeof ConsistencyLevel;
  defaultHeaders?: CosmosHeaders;
  agent?: Agent;
  userAgentSuffix?: string;
  plugins?: PluginConfig[];
}
```

CosmosClientOptions には 11 個のプロパティが定義されています。どうも **CosmosClientOptions の中に endpoint や key をはじめとした Azure Cosmos DB を利用するための各種設定値が格納される**ようです。
*ちなみに TypeScript では、他の言語ではメンバー変数やフィールドと呼ばれる、名前を持ち、指定された型のデータを保持するものをプロパティといいます。*

この CosmosClientOptions のプロパティの 1 つに `ConnectionPolicy`がありますので、先ほどの処理はこのプロパティの値を設定している部分と理解しました。
**ConnectionPolicy は何か**というのを確認するために、さらに中身をみてみます。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/documents/ConnectionPolicy.ts
export interface ConnectionPolicy {
  connectionMode?: ConnectionMode;
  requestTimeout?: number;
  enableEndpointDiscovery?: boolean;
  preferredLocations?: string[];
  retryOptions?: RetryOptions;
  useMultipleWriteLocations?: boolean;
```

ConnectionPolicy の中には、`connectionMode`や`requestTimeout`などの接続に関するポリシー設定があるようです。\(これ以上、クラスを深くみると大変なので、一旦ここまでにします。\)

一旦元に戻って、

```typescript:(再掲)
optionsOrConnectionString.connectionPolicy = Object.assign(
  {},
  defaultConnectionPolicy,
  optionsOrConnectionString.connectionPolicy
);
```

をみると、`Object.assign`を使用しています。
*Object.assign() が何かについては、[こちら][ObjectAssign] を参照してください。*
つまり、CosmosClientOptions の ConnectionPolicy プロパティ内で未定義となっているものについて、デフォルト値を代入している感じです。

### コンストラクタ処理 (3)

```typescript:
optionsOrConnectionString.defaultHeaders = optionsOrConnectionString.defaultHeaders || {};
optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.CacheControl] = "no-cache";
optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.Version] = Constants.CurrentVersion;
if (optionsOrConnectionString.consistencyLevel !== undefined) {
  optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.ConsistencyLevel] = optionsOrConnectionString.consistencyLevel;
}

optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.UserAgent] = getUserAgent(
  optionsOrConnectionString.userAgentSuffix
);
```

この`optionsOrConnectionString.defaultHeaders`は、**CosmosHeaders** クラスです。
中身を見てみます。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/queryExecutionContext/CosmosHeaders.ts
export interface CosmosHeaders {
  [key: string]: string | boolean | number;
}
```

キー\(key\) と キーに紐づく値\(value\) を格納できるようにしています。いわゆる**連想配列**の部分です。
*連想配列をインタフェースで定義するという使い方もあるんですね。勉強になります。*

つまり、ここの処理は、HTTP リクエストの各種ヘッダー情報を CosmosHeaders クラス \(連想配列\) を使って設定しているという感じです。

### コンストラクタ処理 (4)

```typescript
const globalEndpointManager = new GlobalEndpointManager(
  optionsOrConnectionString,
  async (opts: RequestOptions) => this.getDatabaseAccount(opts)
);
```

新しい`GlobalEndpointManager`というヤツが出てきました。
GlobalEndpointManage にある、どのコンストラクタが動いているのかを確認すると

```typescript
(alias) new GlobalEndpointManager(options: CosmosClientOptions, readDatabaseAccount: (opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>): GlobalEndpointManager
import GlobalEndpointManager
```

が動いているようです。中身を確認します。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/globalEndpointManager.ts
export class GlobalEndpointManager {
  private defaultEndpoint: string;
  public enableEndpointDiscovery: boolean;
  private isRefreshing: boolean;
  private options: CosmosClientOptions;
  private preferredLocations: string[];

  constructor(
    options: CosmosClientOptions,
    private readDatabaseAccount: (opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>
  ) {
    this.options = options;
    this.defaultEndpoint = options.endpoint;
    this.enableEndpointDiscovery = options.connectionPolicy.enableEndpointDiscovery;
    this.isRefreshing = false;
    this.preferredLocations = this.options.connectionPolicy.preferredLocations;
  }
}
```

*ナンダコレ、、、*
となったので、TypeDoc を見てみます。

>This internal class implements the logic for endpoint management for geo-replicated database accounts.
>この内部クラスは、地理的に複製されたデータベースアカウントのエンドポイント管理のロジックを実装します。

**ああ、そういうことか!** とこの絵を思い出しました。
*Azure Cosmos DB で、これ忘れたらダメなヤツやん。。*

![azure-cosmos-db.png](https://docs.microsoft.com/ja-jp/azure/cosmos-db/media/introduction/azure-cosmos-db.png)

Microsoft Docs にある [Azure Cosmos DB の概要][MSDocs-Intro] にも一番最初に

>Azure Cosmos DB は、Microsoft によってグローバルに配布されるマルチモデル データベース サービスです。

とあります。
Cosmos DB では、Cosmos アカウントに複数リージョンを関連付けさせることができ、関連付けられたすべてのリージョンにデータがシームレスにレプリケートされるようになっています。これはそれに関連する設定まわりの処理と認識しました。

### コンストラクタ処理 (5)

```typescript
this.clientContext = new ClientContext(optionsOrConnectionString, globalEndpointManager);
```

クライアントコンテキストがやっと出てきました。
どのコンストラクタが動いているのかを確認すると、

```typescript
(alias) new ClientContext(cosmosClientOptions: CosmosClientOptions, globalEndpointManager: GlobalEndpointManager): ClientContext
import ClientContext
```

が動いているようです。

```typescript:Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/ClientContext.ts
export class ClientContext {
  private readonly sessionContainer: SessionContainer;
  private connectionPolicy: ConnectionPolicy;
  public partitionKeyDefinitionCache: { [containerUrl: string]: any };

  public constructor(
    private cosmosClientOptions: CosmosClientOptions,
    private globalEndpointManager: GlobalEndpointManager
  ) {
    this.connectionPolicy = cosmosClientOptions.connectionPolicy;
    this.sessionContainer = new SessionContainer();
    this.partitionKeyDefinitionCache = {};
  }
}
```

\(クライアントコンテキストについては、説明するまでもないと思いますが\) いわゆる、**コンテキストの伝播** とか言われているモノです。
ざっくり言うと、`クライアントに関連する情報を保持している場所`と私は思っています。メソッドの引数にクライアント情報をいちいち与えずに、全部ここを見ましょうよ、的なやつです。\(適当\)
*クライアントコンテキストについて、良い説明となる資料を見つけられなかったので、見つけたら追記したいです。*

### コンストラクタ処理 (6)

```typescript
this.databases = new Databases(this, this.clientContext);
this.offers = new Offers(this, this.clientContext);
```

やっと最後の処理です。
`Databases`は、新しいデータベースの作成、およびすべてのデータベースの読み取り/クエリの操作を行うクラスです。
`Offers`については、聞きなれない単語だと思います。\(筆者もちゃんと理解しているわけではありませんが\) REST経由で、SQL APIを使用する際に登場するもののようです。
*Offer resource というものがあるんですね。別途、学習を進めたいと思います。*

- [Microsoft Docs - Offers][MSDocs-Offers]


# さいごに

今回は、Azure Cosmos DB に JavaScript/Node.js で接続する際に最初に生成される、**CosmosClient** について、中身を確認してみました。
今回の内容は、前回、実際に CRUD アプリを作成 \([前回記事][PrevLink1]\) した時はたったの 1 行で終わってしまった内容です。

普段のアプリ開発では、あまり意識しない世界なのかもしれませんが、`実際にコンストラクタの中で何が行われているのかを確認する`ことは、**Azure Cosmos DB の仕組みや使用するライブラリへの深い知見を得る** ためには必要な事かな、と思いました。

次回は、`Database`クラスについて見ていこうと思います。


# 関連リンク

## 前回記事

- [2020年から始めるAzure Cosmos DB - Node.js で CRUD アプリを作る][PrevLink1]

## 次回記事

- [2020年から始めるAzure Cosmos DB - JavaScript SDK (SQL API)を見てみる (Part.2)][NextLink1]


[NextLink1]:https://qiita.com/ymasaoka/items/f4b2beef93ca1a84af7a


# 参考情報

## npm

- [Microsoft Azure Cosmos JavaScript SDK][npm]

## GitHub

- [Azure/azure-sdk-for-js][GitHub2]
- [Azure/azure-cosmos-js][GitHub1] \(old\)

## Microsoft Docs

- [Azure Cosmos DB のデータを管理する例 (Node.js)][MSDocs]
- [Azure Cosmos DB の概要][MSDocs-Intro]
- [Offers][MSDocs-Offers]

## developer.mozilla.org

- [Object.assign()][ObjectAssign]

[MSDocs]:https://docs.microsoft.com/ja-jp/azure/cosmos-db/sql-api-nodejs-samples
[MSDocs-Intro]:https://docs.microsoft.com/ja-jp/azure/cosmos-db/introduction
[MSDocs-Offers]:https://docs.microsoft.com/en-us/rest/api/cosmos-db/offers
[ObjectAssign]:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign