手軽に使えるデータベース
まあ今更なんですが、思うところありまして、改めてCosmos DBを試してみました。
これまで、RDB以外のドキュメント系データベースだと、私はIBM Cloudantや、そのベースとなってるApache CouchDBを使うことが多く、Cosmos DBはちゃんと使ったことがありませんでした。
私が今働いているDatadogではCosmos DBをモニタリングすることも可能ですし、ちょっとインテグレーション含めて試してみたくて、今回さわってみるに至りました。
やったことは、
まずローカルにCosmos DBを立てる
ローカルのGUIツールでコンテナやデータの操作を試す
Webのインターフェースを作成し、curlを使ってデータのCRUD処理を試す
AzureにCosmos DBを立てる
クライアントアプリの接続先を変更してローカルと同じ用に操作できるか確認する
せっかく試したので、その手順をログとしてこのブログに残します。
ローカルでCosmos DBを使う
Azure Cosmos DB Emulatorのインストール
Microsoft Artifact Registryにイメージがあるので、これを使ってローカルのDocker環境で起動します。
docker run --detach \
--publish 8081:8081 \
--publish 1234:1234 \
--name cosmosdb-emulator \
mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
Localhostにアクセスします。ポートは1234にしたのでその通り指定します。
ここで確認できるURIとプライマリーキーは、後ほどクライアントからの接続に利用します。

コンテナー(データベース)とデータの作成
Data Explorerからデータベースを作成します。
Cloudantには無い概念ですがコンテナーというデータベース相当のインスタンスを作成します。

ここでは以下のように入力しました。
| 項目 | 入力値 |
|---|---|
| Database id | myDatabase(Create new) |
| Container id | myContainer |
| Partition key | /id |
左ペインのHomeの下に「myContainer」が作成されたことが確認できます。

Itemsを選択しても、まだ何もデータが入っていないので、データを登録してみます。

右ペインに以下の通りのJSONデータを記入し、Saveアイコンをクリックして保存します。

{
"id": "1",
"name": "Taiji Hagino",
"city": "Tokyo",
"role": "Senior Technical Advocate"
}
データを更新してみます。
「role」の項目を書き換えて、Updateアイコン(さっきはSaveアイコンだった)をクリックします。

これで、Azure Cosmos DBのGUIツールからの操作は問題なくできることを確認しました。
ちなみに、ツールバー部分に「SELECT * FROM c」というのが見えます。この後ろのテキスト入力エリアにWHERE句を指定することで、条件指定したデータを絞り込むことができます。
これは、Cloudant/Couch DBとは異なる部分で、SQLライクな操作ができる機能です。 好みは分かれるでしょうが、RDBに慣れ親しんだワタシ的には嬉しいかもです(笑)
クライアントアプリの作成
CRUDを実行するクライアントアプリ
Node.jsからCosmos DBエミュレーターに接続してCRUDを実装してみます。
index.js を作成します。ここのエミュレーターの接続設定部分で、先ほど出てきたURIとプライマリーキーを記述します。
const { CosmosClient } = require("@azure/cosmos");
// エミュレーターの接続設定
const endpoint = "http://localhost:8081";
const key = "<Azure Cosmos DB Emulator で発行されたプライマリキー>";
const client = new CosmosClient({ endpoint, key });
async function main() {
// データベース・コンテナの取得(なければ作成)
const { database } = await client.databases.createIfNotExists({ id: "myDatabase" });
const { container } = await database.containers.createIfNotExists({ id: "myContainer" });
// CREATE
console.log("--- CREATE ---");
const { resource: created } = await container.items.create({
id: "2",
name: "John Doe",
city: "New York",
role: "Developer"
});
console.log("作成:", created);
// READ
console.log("\n--- READ ---");
const { resource: item } = await container.item("2", "2").read();
console.log("取得:", item);
// UPDATE
console.log("\n--- UPDATE ---");
item.role = "Senior Developer";
const { resource: updated } = await container.items.upsert(item);
console.log("更新後:", updated);
// DELETE
console.log("\n--- DELETE ---");
await container.item("2", "2").delete();
console.log("削除完了");
// 全件取得
console.log("\n--- 全件取得 ---");
const { resources: allItems } = await container.items.readAll().fetchAll();
console.log("全データ:", allItems);
}
main().catch(console.error);
Node.jsで実装した、データの登録、検索、更新、削除がそれぞれ実行され、その結果がターミナルに出力されたことを確認しました。 最後の「全データ:[]」は、DELETE実行後なので取得結果が0件であることを出力しています。
SQLクエリーを試すクライアントアプリ
今度は、SQLクエリーを実行するアプリを実装します。
index2.js を作成します。
const { CosmosClient } = require("@azure/cosmos");
const endpoint = "http://localhost:8081";
const key = "<Azure Cosmos DB Emulator で発行されたプライマリキー>";
const client = new CosmosClient({ endpoint, key });
async function main() {
const { database } = await client.databases.createIfNotExists({ id: "myDatabase" });
const { container } = await database.containers.createIfNotExists({ id: "myContainer" });
// テストデータを複数登録
console.log("--- テストデータ登録 ---");
const users = [
{ id: "1", name: "Taiji Hagino", city: "Tokyo", role: "MVP", age: 40 },
{ id: "2", name: "John Doe", city: "New York", role: "Developer", age: 30 },
{ id: "3", name: "Jane Smith", city: "Tokyo", role: "Designer", age: 25 },
{ id: "4", name: "Bob Johnson", city: "London", role: "Senior Developer", age: 35 },
{ id: "5", name: "Alice Brown", city: "Tokyo", role: "Developer", age: 28 },
];
for (const user of users) {
await container.items.upsert(user);
}
console.log("5件登録完了\n");
// クエリ1:全件取得
console.log("--- クエリ1:全件取得 ---");
const { resources: all } = await container.items
.query("SELECT * FROM c")
.fetchAll();
console.log(all.map(u => `${u.id}: ${u.name} (${u.city})`));
// クエリ2:WHERE で絞り込み
console.log("\n--- クエリ2:Tokyo在住のみ ---");
const { resources: tokyo } = await container.items
.query("SELECT * FROM c WHERE c.city = 'Tokyo'")
.fetchAll();
console.log(tokyo.map(u => `${u.name} - ${u.role}`));
// クエリ3:ORDER BY
console.log("\n--- クエリ3:年齢順(昇順)---");
const { resources: byAge } = await container.items
.query("SELECT c.name, c.age FROM c ORDER BY c.age ASC")
.fetchAll();
console.log(byAge.map(u => `${u.name}: ${u.age}歳`));
// クエリ4:パラメータ付きクエリ
console.log("\n--- クエリ4:Developerのみ(パラメータ使用)---");
const { resources: devs } = await container.items
.query({
query: "SELECT * FROM c WHERE c.role = @role",
parameters: [{ name: "@role", value: "Developer" }]
})
.fetchAll();
console.log(devs.map(u => `${u.name} (${u.city})`));
// クエリ5:COUNT
console.log("\n--- クエリ5:Tokyo在住の人数 ---");
const { resources: count } = await container.items
.query("SELECT VALUE COUNT(1) FROM c WHERE c.city = 'Tokyo'")
.fetchAll();
console.log(`Tokyo在住: ${count[0]}人`);
}
main().catch(console.error);
コードの中に書いたSQL文が実行され、それぞれの結果がターミナルに出力されました。


Web APIの実装
Expressを使ってデータベースへ接続するWebサービスを作成します。
全件取得、条件指定取得、IDで1件だけ取得、IDをキーに更新、IDをキーに削除、といった感じ。
app.js を作成します。
const express = require("express");
const { CosmosClient } = require("@azure/cosmos");
const app = express();
app.use(express.json());
// Cosmos DB 接続設定
const client = new CosmosClient({
endpoint: "http://localhost:8081",
key: "<Azure Cosmos DB Emulator で発行されたプライマリキー>"
});
const container = client.database("myDatabase").container("myContainer");
// GET /users - 全件取得
app.get("/users", async (req, res) => {
const { resources } = await container.items.readAll().fetchAll();
res.json(resources);
});
// GET /users/search?city=Tokyo&role=Developer など - 条件指定検索
app.get("/users/search", async (req, res) => {
const { city, role, minAge, maxAge } = req.query;
let query = "SELECT * FROM c WHERE 1=1";
const parameters = [];
if (city) {
query += " AND c.city = @city";
parameters.push({ name: "@city", value: city });
}
if (role) {
query += " AND c.role = @role";
parameters.push({ name: "@role", value: role });
}
if (minAge) {
query += " AND c.age >= @minAge";
parameters.push({ name: "@minAge", value: parseInt(minAge) });
}
if (maxAge) {
query += " AND c.age <= @maxAge";
parameters.push({ name: "@maxAge", value: parseInt(maxAge) });
}
const { resources } = await container.items
.query({ query, parameters })
.fetchAll();
res.json(resources);
});
// GET /users/:id - 1件取得
app.get("/users/:id", async (req, res) => {
try {
const { resource } = await container.item(req.params.id, req.params.id).read();
res.json(resource);
} catch {
res.status(404).json({ error: "ユーザーが見つかりません" });
}
});
// POST /users - 新規作成
app.post("/users", async (req, res) => {
const { resource } = await container.items.create(req.body);
res.status(201).json(resource);
});
// PUT /users/:id - 更新
app.put("/users/:id", async (req, res) => {
const item = { ...req.body, id: req.params.id };
const { resource } = await container.items.upsert(item);
res.json(resource);
});
// DELETE /users/:id - 削除
app.delete("/users/:id", async (req, res) => {
await container.item(req.params.id, req.params.id).delete();
res.json({ message: "削除完了" });
});
// サーバー起動
app.listen(3000, () => {
console.log("サーバー起動: http://localhost:3000");
});
実際にcurlで確認してみるとこんな感じに出力されました。 エンドポイントの最後に「?city=Tokyo」とか指定すると、条件に合致するデータが表示されます。
データ追加は、以下のようにPOSTで投げてあげることでデータが追加登録されます。
curl -X PUT http://localhost:3000/users/6 \
-H "Content-Type: application/json" \
-d '{"name":"New User","city":"Osaka","role":"Senior Engineer","age":32}'
AzureでCosmos DBを使う
Azure Cosmos DB の作成
ここでは以下のように作成しました。
| 項目 | 入力値 |
|---|---|
| Workload Type | Learning |
| サブスクリプション | <自分のPAYGサブスクリプション> |
| リソースグループ | (新規) mvp-dev |
| アカウント名 | mycosmosdb-taiji |
| 場所 | Japan East |
| 容量モード | サーバーレス |
後ほどここに表示されているURIを使うので、すぐに確認できるようにしておいてください。

後ほどここに表示されているPRIMARY KEYを使うので、すぐに確認できるようにしておいてください。

コンテナー(データベース)とデータの作成
ローカルでやったのと同じ用に、Data Explorerからデータベースを作成します。
ここではローカルと同じく以下の値を設定しました。
| 項目 | 入力値 |
|---|---|
| Database id | myDatabase(Create new) |
| Container id | myContainer |
| Partition key | /id |
データの投入
Azureに作成したCosmos DBのコンテナにデータを投入します。
先ほど作成したindex2.jsを使いましょう。
index2.jsの接続設定の部分のURIとプライマリーキーの値を、Azure上に作成したCosmos DBのURIとプライマリーキーに置き換えてください。
const { CosmosClient } = require("@azure/cosmos");
const endpoint = "<Azure Cosmos DB で発行されたURI>";
const key = "<Azure Cosmos DB で発行されたプライマリキー>";
const client = new CosmosClient({ endpoint, key });
async function main() {
〜 以後省略
index2.js を実行します。
データが投入され、いくつかのテストクエリーが実行されたことがターミナル上で確認できました。


登録データの確認
Azureのデータエクスプローラーで、先ほど登録したデータが取得できるか確認します。
コンテナー下のItemsの表示を更新すると、レコードが5件登録されていることが確認できました。

Web APIの確認
こちらも同じくapp.jsの接続設定の部分をAzure側のURIとプライマリーキーに置き換えます。
const express = require("express");
const { CosmosClient } = require("@azure/cosmos");
const app = express();
app.use(express.json());
// Cosmos DB 接続設定
const client = new CosmosClient({
endpoint: "<Azure Cosmos DB で発行されたURI>",
key: "<Azure Cosmos DBで発行されたプライマリキー>"
});
const container = client.database("myDatabase").container("myContainer");
〜 以後省略
エンドポイントにcurlでリクエストを送ると正常に結果を返してくれました。

おまけ - DatadogのCosmos DBモニタリング
Datadogでは、Integrationのための入口がたくさん用意されています。(現在1000サービス・機能以上)
Azureのアカウント認証でIntegrationする
DatadogはAzureの各サービスのIntegrationをサポートしています。アカウント認証ベースでつなぐことができ、Teraformなんかも利用可能です。 AzureそのものをIntegrationすることで、ぶら下がっている各機能のモニタリングも開始することができます。
DatadogのダッシュボードでCosmos DBのテレメトリデータを確認する
Integrationが完了すると、デフォルトで取得できるテレメトリデータを監視するためのダッシュボードを自動で作成してくれます。 もちろんカスタマイズ可能なので、見やすいように見出しラベルやレイアウトを変更するのも良いと思います。 スクラッチで自分で1から作成することもできます。
まとめ
データベースって、作成するのも使うのも、作り方やクエリーのお作法が分からないと触ってみるのも億劫になりがちかなと思います。 特にRDBの経験が長くNoSQL系を触ったことない人は顕著かもしれません。(RDBが面倒ですからねー)
まあ、ですが、使ってみたら以外と簡単なものが多いと思うので、まずは単純なCRUDのテストレベルでも、触ってみると面白い発見があるかもしれませんね。




















