メインコンテンツまでスキップ
バージョン: 0.9.0 (最新)

💾 カスタム DataStore の使用

Nodeblocks にはデフォルトで MongoDB の例が含まれていますが、ハンドラーが期待する必要なインターフェースを実装している限り、任意のストレージエンジンを使用できます。この柔軟性により、SQL データベース、Redis、フラットファイル、またはテスト用のインメモリストレージを使用できます。

📋 必要なインターフェース

カスタムデータストアは以下のメソッドを実装する必要があります:

  • insertOne(doc){ insertedId, acknowledged }
  • findOne(filter) → ドキュメントまたは null
  • find(filter){ toArray(): Promise<Record[]> }
  • updateOne(filter, { $set }){ modifiedCount }
  • deleteOne(filter){ deletedCount }

以下では、この契約を示す JSON ファイルアダプターを実装し、サービスと統合する方法を示します。


1️⃣ アダプターの実装

datastore/jsonFileDataStore.ts
import {promises as fs} from 'fs';

interface DbRecord {
id: string;
[key: string]: unknown;
}
interface QueryFilter {
id?: string;
[key: string]: unknown;
}
interface UpdateOperation {
$set: Record<string, unknown>;
}

const dbFile = 'db.json';

export const jsonFileDataStore = {
/* Create */
async insertOne(doc: DbRecord) {
const records = await readAll();
records.push(doc);
await writeAll(records);
return { insertedId: doc.id, acknowledged: true };
},

/* Read single */
async findOne(query: QueryFilter) {
const records = await readAll();
return records.find((r) => match(r, query)) ?? null;
},

/* Read many */
find(query: QueryFilter = {}) {
return {
async toArray() {
const records = await readAll();
return records.filter((r) => match(r, query));
},
};
},

/* Update */
async updateOne(query: QueryFilter, update: UpdateOperation) {
const records = await readAll();
const idx = records.findIndex((r) => match(r, query));
if (idx === -1) return { modifiedCount: 0, acknowledged: true };
records[idx] = { ...records[idx], ...update.$set };
await writeAll(records);
return { modifiedCount: 1, acknowledged: true };
},

/* Delete */
async deleteOne(query: QueryFilter) {
const records = await readAll();
const newRecords = records.filter((r) => !match(r, query));
await writeAll(newRecords);
return { deletedCount: records.length - newRecords.length, acknowledged: true };
},
};

/* ------------------------------------- */

function match(record: DbRecord, query: QueryFilter) {
return Object.entries(query).every(([k, v]) => record[k] === v);
}

async function readAll(): Promise<DbRecord[]> {
try {
const raw = await fs.readFile(dbFile, 'utf8');
return JSON.parse(raw);
} catch {
return [];
}
}

async function writeAll(records: DbRecord[]) {
await fs.writeFile(dbFile, JSON.stringify(records, null, 2));
}

なぜこれらのメソッドなのか?

Nodeblocks ハンドラーは上記のメソッドのみを呼び出します。ストレージテクノロジー(SQL、Redis、REST API など)がこれらのメソッドを実装できる場合、サービスコードを変更せずに使用できます。この抽象化により、互換性を維持しながら最大限の柔軟性が提供されます。


2️⃣ カスタム DataStore の使用

サービスは依存性注入を使用するため、サービスを作成するときにカスタムデータストアを渡します:

import {services} from '@nodeblocks/backend-sdk';
import {jsonFileDataStore} from './datastore/jsonFileDataStore';

-- snap --
services.userService({users: jsonFileDataStore}, {});
-- snap --

これで、userServicedb.json にデータを永続化します。

注意: この例は、ユーザーサービス専用に設計されています。他のサービス(organizationServiceproductService など)は、上記のコア CRUD 操作以外に、特殊なクエリメソッドや集約関数などの追加のデータベースメソッドを必要とする場合があります。


3️⃣ 実装チェックリスト

カスタムデータストアを実装する際は、以下を確認してください:

  1. メソッドシグネチャがハンドラーが期待するものと一致する(insertOnefindOne など)
  2. 戻り値の型に必要なフィールドが含まれる(insertedIdmodifiedCount など)
  3. 非同期サポート - すべてのメソッドは Promise を返す必要があります(ハンドラーはそれらを非同期として扱うため)
  4. エラーハンドリング - クラッシュを防ぐために適切なエラーハンドリングを実装

これらの条件が満たされると、フラットファイル、SQL データベース、クラウド関数、またはテスト用のインメモリモックを完全に自由に使用できます。


🧪 モック DataStore でのテスト

ユニットテストでは、同じインターフェースに従うシンプルなインメモリ実装を作成できます:

export const memoryDataStore = {
_data: [] as any[],
async insertOne(doc) { this._data.push(doc); return { insertedId: doc.id }; },
async findOne(q) { return this._data.find((r) => r.id === q.id); },
find() { return { toArray: async () => this._data }; },
async updateOne(q, u) { /* ... */ return { modifiedCount: 1 }; },
async deleteOne(q) { /* ... */ return { deletedCount: 1 }; },
};

このモックデータストアは、以下の理由でテストに最適です:

  • I/O が不要 - すべての操作がメモリ内で実行される
  • 同じインターフェースに従う - 任意の実際のデータストアと交換可能
  • 高速なテストを提供 - データベースのセットアップやクリーンアップが不要

🔧 一般的な使用例

開発とテスト

  • JSON ファイル - シンプルなセットアップ、人間が読めるデータ
  • インメモリストレージ - 高速なテスト、永続化が不要
  • SQLite - ファイルベースの SQL データベース、サーバーが不要

本番環境

  • PostgreSQL/MySQL - 堅牢でスケーラブルなリレーショナルデータベース
  • MongoDB - 豊富なクエリ機能を持つドキュメントデータベース
  • Redis - 高性能なキャッシングとセッションストレージ

クラウドとサーバーレス

  • DynamoDB - AWS 管理の NoSQL データベース
  • Firestore - Google Cloud ドキュメントデータベース
  • CosmosDB - Azure マルチモデルデータベース

✅ まとめ

  • インターフェース準拠 - 正しいシグネチャで5つの必要なメソッドを実装
  • 依存性注入 - サービスを作成するときにデータストアを渡す
  • ストレージの柔軟性 - インターフェースを実装できる任意のストレージテクノロジーを使用
  • テストサポート - 高速で信頼性の高いユニットテスト用のインメモリモックを作成
  • 本番環境対応 - コード変更なしで開発と本番環境のデータストアを切り替え

この抽象化により、すべての Nodeblocks サービスとの互換性を維持しながら、ニーズに適したストレージソリューションを完全に自由に選択できます。