💾 カスタムデータストアの使用
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️⃣ アダプタを実装
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 = {
/* 作成 */
async insertOne(doc: DbRecord) {
const records = await readAll();
records.push(doc);
await writeAll(records);
return { insertedId: doc.id, acknowledged: true };
},
/* 単一読み取り */
async findOne(query: QueryFilter) {
const records = await readAll();
return records.find((r) => match(r, query)) ?? null;
},
/* 複数読み取り */
find(query: QueryFilter = {}) {
return {
async toArray() {
const records = await readAll();
return records.filter((r) => match(r, query));
},
};
},
/* 更新 */
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 };
},
/* 削除 */
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️⃣ カスタムデータストアを使用
サービスが依存性注入を使用するため、サービスを作成する際にカスタムデータストアを渡します:
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データベース、クラウド関数、またはテスト用のインメモリモックを使用する完全な自由を得られます。
🧪 モックデータストアでのテスト
単体テストでは、同じインターフェースに従うシンプルなインメモリ実装を作成できます:
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サービスとの互換性を維持しながら、ニーズに合ったストレージソリューションを自由に選択できます。