对象多实例跨文件共享的设计模式选型及MongoClient配置疑问
MongoDB多实例共享与实现方案解析
现有场景需创建两个MongoClient实例:一个连接本地MongoDB服务器,另一个连接远程MongoDB,当前将这两个实例定义在database-connector.js文件中,供gui.js、model.js等文件导入使用。同时有三个疑问:
- 当对象需多次创建并在不同文件间共享时,应采用何种设计模式?
- 当前的MongoClient配置方式是否正确?
- 正确的实现方案是什么?
原代码片段
database-connector.ts
class CatchError { static error: string | unknown; static save(error: string | unknown) { this.error = error; const re = new RegExp('ECONNREFUSED', 'i') if (re.test(error.message)) { // fs.write('system.error.log') } } } class DBClient { client: MongoClient; constructor(url: string, config: Partial<MongoClientOptions>) { this.client = new MongoClient(url, config) } async connect() { try { this.client = await this.client.connect(); return this.client; } catch(error) { CatchError.parse(error) } } } // for entire data application export const localDBClient = initializeDBConnection("mongodb://localhost"); // only for authentication to access application export const remoteDBClient = initializeDBConnection("mongodb+srv://se132.aws-slcuster.com");
gui.js
import { remoteDBClient } from "database-connector.js"; // use client //
model.js
import { localDBClient } from "database-connector.js"; // use client //
问题解答
1. 适用的设计模式
当需要在多个文件间共享固定数量的对象实例时,**多例模式(Multiton)**是最优选择——它是单例模式的扩展,允许类根据唯一标识创建并维护多个实例,避免重复初始化资源(比如数据库连接池)。如果只需要单一实例,则用单例模式即可。
2. 当前配置方式的问题
当前实现存在多处错误和隐患:
- 未定义核心函数:代码中直接调用
initializeDBConnection但未实现,会直接报错。 - 方法调用错误:
CatchError类只有save方法,却调用了不存在的parse方法。 - 异步逻辑处理不当:如果
initializeDBConnection是异步操作,导出的localDBClient/remoteDBClient会是Promise对象,其他文件直接使用会出错。 - 静态错误存储风险:
CatchError用静态属性存错误,异步场景下会被覆盖,无法保留历史错误。 - 连接逻辑不严谨:
DBClient的connect方法会覆盖原有client实例,且未处理连接失败后的抛出逻辑,导致调用方无法感知错误。
3. 正确的实现方案
以下是修正后的完整实现,基于多例模式,同时解决异步连接、错误处理等问题:
database-connector.ts
import { MongoClient, MongoClientOptions } from 'mongodb'; import fs from 'fs/promises'; class CatchError { // 改用实例属性存储错误,避免静态属性的共享冲突 error: string | unknown; save(error: string | unknown) { this.error = error; const re = new RegExp('ECONNREFUSED', 'i'); // 异步写入日志,不阻塞主线程 if (error instanceof Error && re.test(error.message)) { fs.appendFile('system.error.log', `${new Date().toISOString()}: ${error.message}\n`) .catch(logErr => console.error('写入错误日志失败:', logErr)); } } } class DBClient { // 用Map存储不同标识的实例,实现多例模式 private static instances: Map<string, DBClient> = new Map(); client: MongoClient | null = null; private catchError = new CatchError(); // 私有构造函数,禁止外部直接实例化 private constructor(private url: string, private config: Partial<MongoClientOptions> = {}) {} // 获取或创建实例的静态入口 static getInstance(key: string, url: string, config: Partial<MongoClientOptions> = {}): DBClient { if (!DBClient.instances.has(key)) { DBClient.instances.set(key, new DBClient(url, config)); } return DBClient.instances.get(key)!; } // 连接数据库,复用已有连接 async connect(): Promise<MongoClient> { // 检查现有连接是否可用,避免重复创建 if (this.client && this.client.topology?.isConnected()) { return this.client; } try { const client = new MongoClient(this.url, this.config); this.client = await client.connect(); console.log(`成功连接到数据库: ${this.url}`); return this.client; } catch (error) { this.catchError.save(error); throw error; // 抛出错误让调用方处理,避免静默失败 } } // 便捷方法:直接获取数据库实例 async getDatabase(dbName: string) { const client = await this.connect(); return client.db(dbName); } } // 初始化两个全局实例,供其他文件导入 export const localDBClient = DBClient.getInstance('local', 'mongodb://localhost'); export const remoteDBClient = DBClient.getInstance('remote', 'mongodb+srv://se132.aws-slcuster.com');
gui.js(使用示例)
import { remoteDBClient } from "./database-connector.js"; async function initAuth() { try { const db = await remoteDBClient.getDatabase('auth_db'); // 执行认证相关操作 const adminUser = await db.collection('users').findOne({ username: 'admin' }); console.log('查询到管理员:', adminUser); } catch (err) { console.error('认证数据库操作失败:', err); } } initAuth();
model.js(使用示例)
import { localDBClient } from "./database-connector.js"; async function fetchAppData() { try { const db = await localDBClient.getDatabase('app_data'); // 获取前10条记录 const records = await db.collection('records').find({}).limit(10).toArray(); console.log('查询到数据:', records); } catch (err) { console.error('本地数据库操作失败:', err); } } fetchAppData();
关键优化点
- 多例模式保障实例唯一性:用
Map存储不同标识的实例,确保每个数据库连接只初始化一次。 - 异步连接安全处理:通过
connect方法确保连接完成后再返回实例,避免导出Promise导致的使用问题。 - 错误处理更健壮:改用实例化的
CatchError存储错误,异步写入日志,同时抛出错误让调用方感知。 - 连接复用:检查现有连接状态,避免重复创建连接池,提升性能。
内容的提问来源于stack exchange,提问作者imagesck
相关产品推荐
相关产品推荐

