基于Typegoose与NestJS框架实现多租户:动态创建独立数据库连接的可行性咨询
实现Typegoose + NestJS的动态多租户数据库连接
当然可以实现!我之前在NestJS + Typegoose的项目里做过类似的多租户独立数据库方案,核心思路是动态创建Mongoose连接,再将这些连接绑定给Typegoose模型,从而让不同租户的模型操作各自的专属数据库。
核心原理
Typegoose本质是Mongoose的封装,所以动态连接的关键在于管理Mongoose的Connection实例,然后在创建Typegoose模型时指定对应的existingConnection参数,替代默认的全局连接。
具体实现步骤
1. 构建租户数据库连接管理服务
首先创建一个全局服务,用来维护所有租户的数据库连接池,避免重复创建连接:
import { Injectable, OnModuleDestroy } from '@nestjs/common'; import { createConnection, Connection } from 'mongoose'; import { getModelForClass, ClassType } from '@typegoose/typegoose'; @Injectable() export class TenantDbService implements OnModuleDestroy { // 用Map存储租户ID与对应连接的映射 private tenantConnections: Map<string, Connection> = new Map(); /** * 获取或创建租户专属连接 * @param tenantId 租户唯一标识 * @param dbConfig 租户数据库配置(URI、用户名、密码等) */ async getTenantConnection(tenantId: string, dbConfig: { uri: string }): Promise<Connection> { // 连接已存在则直接返回 if (this.tenantConnections.has(tenantId)) { return this.tenantConnections.get(tenantId)!; } // 创建新连接 const connection = await createConnection(dbConfig.uri, { // 可根据需求添加Mongoose连接选项 autoIndex: false, }).asPromise(); this.tenantConnections.set(tenantId, connection); return connection; } /** * 获取租户专属模型 * @param tenantId 租户唯一标识 * @param dbConfig 租户数据库配置 * @param modelClass Typegoose模型类 */ async getTenantModel<T>( tenantId: string, dbConfig: { uri: string }, modelClass: ClassType<T>, ) { const connection = await this.getTenantConnection(tenantId, dbConfig); // 指定existingConnection绑定到租户连接 return getModelForClass(modelClass, { existingConnection: connection }); } // 模块销毁时关闭所有租户连接,避免资源泄漏 async onModuleDestroy() { await Promise.all( Array.from(this.tenantConnections.values()).map((conn) => conn.close()), ); } }
2. 定义Typegoose模型类
和普通模型定义一样,不需要额外修改,只需要确保模型类是可复用的:
import { modelOptions, Prop } from '@typegoose/typegoose'; @modelOptions({ schemaOptions: { collection: 'products' } }) export class Product { @Prop({ required: true }) name: string; @Prop({ required: true, type: Number }) price: number; }
3. 在控制器/服务中动态使用租户模型
在业务逻辑中,根据当前租户ID获取对应的模型,再执行数据库操作:
import { Controller, Get, Param } from '@nestjs/common'; import { TenantDbService } from './tenant-db.service'; import { Product } from './product.model'; // 假设从URL参数获取租户ID @Controller('tenants/:tenantId/products') export class TenantProductController { constructor(private readonly tenantDbService: TenantDbService) {} @Get() async getProducts(@Param('tenantId') tenantId: string) { // 这里建议从主数据库/配置中心获取租户的数据库配置,而非硬编码 const tenantDbConfig = { uri: `mongodb://localhost:27017/tenant_${tenantId}`, }; // 获取当前租户的Product模型 const ProductModel = await this.tenantDbService.getTenantModel( tenantId, tenantDbConfig, Product, ); // 执行常规的数据库操作 return ProductModel.find().exec(); } }
关键注意事项
- 连接池优化:定期清理闲置的租户连接,或者在租户删除时主动关闭对应连接,避免占用过多数据库连接资源。
- 配置安全:租户的数据库凭证(用户名、密码)不要硬编码,建议存储在加密的配置中心或主数据库中,使用时动态获取。
- 事务支持:由于每个租户是独立数据库,跨租户事务无法实现,只能在单个租户的连接内处理事务。
- 全局模块注册:将
TenantDbService注册为全局模块,方便在整个应用的控制器和服务中注入使用。
内容的提问来源于stack exchange,提问作者GAURAV RAJPUT
相关产品推荐
相关产品推荐

