この記事について
本記事は、2020年3月6日 (米国時間) にて、Azure Cosmos DB に新しく Free Tier (無償利用枠) が登場したことに伴い、改めて Azure Cosmos DB を色々と触っていく試みの 3 回目です。
今回は、前回記事 にて作成した CRUD アプリ内で使用している Microsoft Azure Cosmos JavaScript SDK について見ていきたいと思います。
対象読者
- Azure Cosmos DB について学習したい方
- Node.js で Azure Cosmos DB への CRUD 操作を行いたい方
- Microsoft Azure Cosmos JavaScript SDK の動作について理解したい方
Microsoft Azure Cosmos JavaScript SDK
実際に、Microsoft Docs の内容を元に、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データベースサービスで要求を構成および実行するために使用されます。
const client: CosmosClient = new CosmosClient({ endpoint, key });
上にある通り、endpoint と key を使用してインスタンスを作成しています。
- endpoint: Azure Cosmos アカウント URI、
- key: Azure Cosmos アカウントのプライマリキー or セカンダリキー
どのコンストラクタが動いているのかを確認すると
(alias) new CosmosClient(options: CosmosClientOptions): CosmosClient (+1 overload)
import CosmosClient
とあり、CosmosClient(options: CosmosClientOptions)
が動いているようです。
中身を確認してみます。
/**
* 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)
if (typeof optionsOrConnectionString === "string") {
optionsOrConnectionString = parseConnectionString(optionsOrConnectionString);
}
これは単純に、コンストラクタの引数がstring
かCosmosClientOptions
かを判別しています。
引数が string の場合は、if 文の中で接続文字列を使って CosmosClientOptions を生成し、 optionsOrConnectionString に代入しています。
コンストラクタの引数が string と CosmosClientOptions と異なっているので、お決まりな感じの処理です。
コンストラクタの引数の型が違うからと言い、内容がほとんど重複するようなコンストラクタをしっかり分けて書く人をたまに見かけます。このコードは「そんな無駄なことしなくていいよ」と教えてくれるいい例ですね。
コンストラクタ処理 (2)
optionsOrConnectionString.connectionPolicy = Object.assign(
{},
defaultConnectionPolicy,
optionsOrConnectionString.connectionPolicy
);
ここでは、CosmosClientOptions の connectionPolicy
に値を設定しています。
その前に、CosmosClientOptions って何者だ?、という疑問があるので、先にこちらを見てみます。
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 は何かというのを確認するために、さらに中身をみてみます。
export interface ConnectionPolicy {
connectionMode?: ConnectionMode;
requestTimeout?: number;
enableEndpointDiscovery?: boolean;
preferredLocations?: string[];
retryOptions?: RetryOptions;
useMultipleWriteLocations?: boolean;
ConnectionPolicy の中には、connectionMode
やrequestTimeout
などの接続に関するポリシー設定があるようです。(これ以上、クラスを深くみると大変なので、一旦ここまでにします。)
一旦元に戻って、
optionsOrConnectionString.connectionPolicy = Object.assign(
{},
defaultConnectionPolicy,
optionsOrConnectionString.connectionPolicy
);
をみると、Object.assign
を使用しています。
Object.assign() が何かについては、こちら を参照してください。
つまり、CosmosClientOptions の ConnectionPolicy プロパティ内で未定義となっているものについて、デフォルト値を代入している感じです。
コンストラクタ処理 (3)
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 クラスです。
中身を見てみます。
export interface CosmosHeaders {
[key: string]: string | boolean | number;
}
キー(key) と キーに紐づく値(value) を格納できるようにしています。いわゆる連想配列の部分です。
連想配列をインタフェースで定義するという使い方もあるんですね。勉強になります。
つまり、ここの処理は、HTTP リクエストの各種ヘッダー情報を CosmosHeaders クラス (連想配列) を使って設定しているという感じです。
コンストラクタ処理 (4)
const globalEndpointManager = new GlobalEndpointManager(
optionsOrConnectionString,
async (opts: RequestOptions) => this.getDatabaseAccount(opts)
);
新しいGlobalEndpointManager
というヤツが出てきました。
GlobalEndpointManage にある、どのコンストラクタが動いているのかを確認すると
(alias) new GlobalEndpointManager(options: CosmosClientOptions, readDatabaseAccount: (opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>): GlobalEndpointManager
import GlobalEndpointManager
が動いているようです。中身を確認します。
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 で、これ忘れたらダメなヤツやん。。
Microsoft Docs にある Azure Cosmos DB の概要 にも一番最初に
Azure Cosmos DB は、Microsoft によってグローバルに配布されるマルチモデル データベース サービスです。
とあります。
Cosmos DB では、Cosmos アカウントに複数リージョンを関連付けさせることができ、関連付けられたすべてのリージョンにデータがシームレスにレプリケートされるようになっています。これはそれに関連する設定まわりの処理と認識しました。
コンストラクタ処理 (5)
this.clientContext = new ClientContext(optionsOrConnectionString, globalEndpointManager);
クライアントコンテキストがやっと出てきました。
どのコンストラクタが動いているのかを確認すると、
(alias) new ClientContext(cosmosClientOptions: CosmosClientOptions, globalEndpointManager: GlobalEndpointManager): ClientContext
import ClientContext
が動いているようです。
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)
this.databases = new Databases(this, this.clientContext);
this.offers = new Offers(this, this.clientContext);
やっと最後の処理です。
Databases
は、新しいデータベースの作成、およびすべてのデータベースの読み取り/クエリの操作を行うクラスです。
Offers
については、聞きなれない単語だと思います。(筆者もちゃんと理解しているわけではありませんが) REST経由で、SQL APIを使用する際に登場するもののようです。
Offer resource というものがあるんですね。別途、学習を進めたいと思います。
さいごに
今回は、Azure Cosmos DB に JavaScript/Node.js で接続する際に最初に生成される、CosmosClient について、中身を確認してみました。
今回の内容は、前回、実際に CRUD アプリを作成 (前回記事) した時はたったの 1 行で終わってしまった内容です。
普段のアプリ開発では、あまり意識しない世界なのかもしれませんが、実際にコンストラクタの中で何が行われているのかを確認する
ことは、Azure Cosmos DB の仕組みや使用するライブラリへの深い知見を得る ためには必要な事かな、と思いました。
次回は、Database
クラスについて見ていこうと思います。