我想连接到基于子域( Multi-Tenancy )的任何数据库,但我不知道该怎么做。

我的代码在应用程序启动时运行,但我不知道如何根据子域更改数据源。

PS:我根据每个请求创建了中间件,但是我不知道如何更改源。

我的数据库有以下代码:

import { connect, createConnection } from 'mongoose';
import { SERVER_CONFIG, DB_CONNECTION_TOKEN } from '../server.constants';

 const opts = {
    useCreateIndex: true,
    useNewUrlParser: true,
    keepAlive: true,
    socketTimeoutMS: 30000,
    poolSize: 100,
    reconnectTries: Number.MAX_VALUE,
    reconnectInterval: 500,
    autoReconnect: true,
  };
export const databaseProviders = [
  {
    provide: DB_CONNECTION_TOKEN,
    useFactory: async () => {
      try {
        console.log(`Connecting to ${ SERVER_CONFIG.db }`);
        return await createConnection(`${SERVER_CONFIG.db}`, opts);
      } catch (ex) {
        console.log(ex);
      }

    },
  }
];

我想根据子域( Multi-Tenancy )在每个请求中更改我的数据源

最佳答案

这是我与 Mongoose 一起使用的解决方案

  • TenantsService 用于管理应用程序中的所有租户

  • @Injectable()
    export class TenantsService {
        constructor(
            @InjectModel('Tenant') private readonly tenantModel: Model<ITenant>,
        ) {}
    
        /**
         * Save tenant data
         *
         * @param {CreateTenantDto} createTenant
         * @returns {Promise<ITenant>}
         * @memberof TenantsService
         */
        async create(createTenant: CreateTenantDto): Promise<ITenant> {
            try {
                const dataToPersist = new this.tenantModel(createTenant);
                // Persist the data
                return await dataToPersist.save();
            } catch (error) {
                throw new HttpException(error, HttpStatus.BAD_REQUEST);
            }
        }
    
        /**
         * Find details of a tenant by name
         *
         * @param {string} name
         * @returns {Promise<ITenant>}
         * @memberof TenantsService
         */
        async findByName(name: string): Promise<ITenant> {
            return await this.tenantModel.findOne({ name });
        }
    }
    
    
  • TenantAwareMiddleware 中间件,用于从请求上下文中获取 tenant id。您可以在此处创建自己的逻辑以从请求 header 或请求 url 子域中提取 tenant id 。请求头提取方法如下所示。

  • 如果要提取子域,则可以通过调用Requestreq.subdomains对象中提取子域来完成,这将为您提供子域列表,然后您可以从中找到想要的子域。

    @Injectable()
    export class TenantAwareMiddleware implements NestMiddleware {
        async use(req: Request, res: Response, next: NextFunction) {
            // Extract from the request object
            const { subdomains, headers } = req;
    
            // Get the tenant id from header
            const tenantId = headers['X-TENANT-ID'] || headers['x-tenant-id'];
    
            if (!tenantId) {
                throw new HttpException('`X-TENANT-ID` not provided', HttpStatus.NOT_FOUND);
            }
    
            // Set the tenant id in the header
            req['tenantId'] = tenantId.toString();
    
            next();
        }
    }
    
  • TenantConnection 此类用于使用 tenant id 创建新连接,如果现有连接可用,它将返回相同的连接(以避免创建额外的连接)。

  • @Injectable()
    export class TenantConnection {
        private _tenantId: string;
    
        constructor(
            private tenantService: TenantsService,
            private configService: ConfigService,
        ) {}
    
        /**
         * Set the context of the tenant
         *
         * @memberof TenantConnection
         */
        set tenantId(tenantId: string) {
            this._tenantId = tenantId;
        }
    
        /**
         * Get the connection details
         *
         * @param {ITenant} tenant
         * @returns
         * @memberof TenantConnection
         */
        async getConnection(): Connection {
            // Get the tenant details from the database
            const tenant = await this.tenantService.findByName(this._tenantId);
    
            // Validation check if tenant exist
            if (!tenant) {
                throw new HttpException('Tenant not found', HttpStatus.NOT_FOUND);
            }
    
            // Get the underlying mongoose connections
            const connections: Connection[] = mongoose.connections;
    
            // Find existing connection
            const foundConn = connections.find((con: Connection) => {
                return con.name === `tenantDB_${tenant.name}`;
            });
    
            // Check if connection exist and is ready to execute
            if (foundConn && foundConn.readyState === 1) {
                return foundConn;
            }
    
            // Create a new connection
            return await this.createConnection(tenant);
        }
    
        /**
         * Create new connection
         *
         * @private
         * @param {ITenant} tenant
         * @returns {Connection}
         * @memberof TenantConnection
         */
        private async createConnection(tenant: ITenant): Promise<Connection> {
            // Create or Return a mongo connection
            return await mongoose.createConnection(`${tenant.uri}`, this.configService.get('tenant.dbOptions'));
        }
    }
    
    
  • TenantConnectionFactory 这是自定义提供程序,它为您提供 tenant id 并有助于创建连接

  • // Tenant creation factory
    export const TenantConnectionFactory = [
        {
            provide: 'TENANT_CONTEXT',
            scope: Scope.REQUEST,
            inject: [REQUEST],
            useFactory: (req: Request): ITenantContext => {
                const { tenantId } = req as any;
                return new TenantContext(tenantId);
            },
        },
        {
            provide: 'TENANT_CONNECTION',
            useFactory: async (context: ITenantContext, connection: TenantConnection): Promise<typeof mongoose>  => {
                // Set tenant context
                connection.tenantId = context.tenantId;
    
                // Return the connection
                return connection.getConnection();
            },
            inject: ['TENANT_CONTEXT', TenantConnection],
        },
    ];
    
  • TenantsModule - 在这里您可以看到 TenantConnectionFactory 作为提供者添加并被导出以在其他模块中使用。

  • @Module({
      imports: [
        CoreModule,
      ],
      controllers: [TenantsController],
      providers: [
        TenantsService,
        TenantConnection,
        ...TenantConnectionFactory,
      ],
      exports: [
        ...TenantConnectionFactory,
      ],
    })
    export class TenantsModule {}
    
  • TenantModelProviders - 由于您的租户模型取决于租户连接,您的模型必须通过提供者定义,然后包含在您初始化它们的模块中。

  • export const TenantModelProviders = [
        {
            provide: 'USER_MODEL',
            useFactory: (connection: Connection) => connection.model('User', UserSchema),
            inject: ['TENANT_CONNECTION'],
        },
    ];
    
  • UsersModule - 这个类将使用模型。您还可以看到这里配置的中间件对您的租户数据库路由起作用。在这种情况下,所有 user 路由都是租户的一部分,将由租户 db 提供服务。

  • @Module({
      imports: [
        CoreModule,
        TenantsModule,
      ],
      providers: [
        UsersService,
        ...TenantModelProviders,
      ],
      controllers: [UsersController],
    })
    export class UsersModule implements NestModule {
      configure(context: MiddlewareConsumer) {
        context.apply(TenantAwareMiddleware).forRoutes('/users');
      }
    }
    
  • UsersService - 从用户模块
  • 访问租户数据库的示例实现

    @Injectable()
    export class UsersService {
    
        constructor(
            @Inject('TENANT_CONTEXT') readonly tenantContext: ITenantContext,
            @Inject('USER_MODEL') private userModel: Model<IUser>,
        ) {
            Logger.debug(`Current tenant: ${this.tenantContext.tenantId}`);
        }
    
        /**
         * Create a new user
         *
         * @param {CreateUserDto} user
         * @returns {Promise<IUser>}
         * @memberof UsersService
         */
        async create(user: CreateUserDto): Promise<IUser> {
            try {
                const dataToPersist = new this.userModel(user);
                // Persist the data
                return await dataToPersist.save();
            } catch (error) {
                throw new HttpException(error, HttpStatus.BAD_REQUEST);
            }
        }
    
        /**
         * Get the list of all users
         *
         * @returns {Promise<IUser>}
         * @memberof UsersService
         */
        async findAll(): Promise<IUser> {
            return await this.userModel.find({});
        }
    }
    
    

    关于mongodb - 我如何在 NESTJS 中设置 Multi-Tenancy ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56085705/

    10-11 00:59
    查看更多