💾 カスタム DataStore の使用
Nodeblocks にはデフォルトで MongoDB の例が含まれていますが、ハンドラーが期待する必要なインターフェースを実装している限り、任意のストレージエンジンを使用できます。この柔軟性により、SQL データベース、Redis、フラットファイル、またはテスト用のインメモリストレージを使用できます。
📋 必要なインターフェース
カスタムデータストアは以下のメソッドを実装する必要があります:
insertOne(doc)→{ insertedId, acknowledged }findOne(filter)→ ドキュメントまたはnullfind(filter)→{ toArray(): Promise<Record[]> }updateOne(filter, { $set })→{ modifiedCount }deleteOne(filter)→{ deletedCount }
以下では、この契約を示す JSON ファイルアダプターを実装し、サービスと統合する方法を示します。
1️⃣ アダプターの実装
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 --
これで、userService は db.json にデータを永続化します。
注意: この例は、ユーザーサービス専用に設計されています。他のサービス(organizationService、productService など)は、上記のコア CRUD 操作以外に、特殊なクエリメソッドや集約関数などの追加のデータベースメソッドを必要とする場合があります。
3️⃣ 実装チェックリスト
カスタムデータストアを実装する際は、以下を確認してください:
- メソッドシグネチャがハンドラーが期待するものと一致する(
insertOne、findOneなど) - 戻り値の型に必要なフィールドが含まれる(
insertedId、modifiedCountなど) - 非同期サポート - すべてのメソッドは Promise を返す必要があります(ハンドラーはそれらを非同期として扱うため)
- エラーハンドリング - クラッシュを防ぐために適切なエラーハンドリングを実装
これらの条件が満たされると、フラットファイル、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 サービスとの互換性を維持しながら、ニーズに適したストレージソリューションを完全に自由に選択できます。