NestJS:按请求(子域)进行数据库连接(TypeORM)

yoh*_*yoh 7 typeorm nestjs

我正在尝试通过Nest / TypeORM构建SAAS产品,并且需要按子域配置/更改数据库连接。

customer1.domain.com => connect to customer1 database
customer2.domain.com => connect to customer2 database
x.domain.com => connect to x database
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点 ?使用拦截器或请求上下文(或Zone.js)?

我不知道如何开始。有人已经这样做了吗?


WIP:我目前在做什么:

  1. 将所有连接设置添加到ormconfig文件中
  2. 在所有路由上创建中间件以将子域注入res.locals(实例名称)并创建/警告typeorm连接

    import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
    import { getConnection, createConnection } from "typeorm";
    
    @Injectable()
    export class DatabaseMiddleware implements NestMiddleware {
        resolve(): MiddlewareFunction {
          return async (req, res, next) => {
              const instance = req.headers.host.split('.')[0]
              res.locals.instance = instance
    
              try {
                  getConnection(instance)
              } catch (error) {
                  await createConnection(instance)
              }
    
              next();
          };
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在Controller中:从@Response获取实例名称并将其传递给我的服务

    @Controller('/catalog/categories')
    export class CategoryController {
        constructor(private categoryService: CategoryService) {}
    
        @Get()
        async getList(@Query() query: SearchCategoryDto, @Response() response): Promise<Category[]> {
          return response.send(
            await this.categoryService.findAll(response.locals.instance, query)
          )
        }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 在Service中:获取给定实例的TypeORM Manager,并通过存储库查询数据库

    @Injectable()
    export class CategoryService {
      // constructor(
      //   @InjectRepository(Category) private readonly categoryRepository: Repository<Category>
      // ) {}
    
      async getRepository(instance: string): Promise<Repository<Category>> {
          return (await getManager(instance)).getRepository(Category)
      }
    
      async findAll(instance: string, dto: SearchCategoryDto): Promise<Category[]> {
        let queryBuilder = (await this.getRepository(instance)).createQueryBuilder('category')
    
        if (dto.name) {
            queryBuilder.andWhere("category.name like :name", { name: `%${dto.name}%` })
        }
    
        return await queryBuilder.getMany();
      }
    
    Run Code Online (Sandbox Code Playgroud)

似乎可行,但我不确定几乎所有内容:

  • 连接池(我可以在ConnectionManager中创建多少个连接?)
  • 传递子域到response.locals ...不好的做法?
  • 可读性/理解力/添加大量其他代码...
  • 副作用:恐怕在几个子域之间共享连接
  • 副作用:性能

处理response.send()+ Promise + await(s)+在任何地方传递子域都不是一件令人愉快的事情...

有没有一种方法可以直接将子域加入我的服务?

有没有一种方法可以将正确的子域连接/存储库直接添加到我的服务中并将其注入到我的控制器中?

小智 6

我想出了另一个解决方案。

我创建了一个中间件来获取特定租户的连接:

import { createConnection, getConnection } from 'typeorm';
import { Tenancy } from '@src/tenancy/entity/tenancy.entity';

export function tenancyConnection(...modules: Array<{ new(...args: any[]): 
any; }>) {

  return async (req, res, next) => {

    const tenant = req.headers.host.split(process.env.DOMAIN)[0].slice(0, -1);

    // main database connection
    let con = ...

    // get db config that is stored in the main db
    const tenancyRepository = await con.getRepository(Tenancy);
    const db_config = await tenancyRepository.findOne({ subdomain: tenant });

    let connection;
    try {
       connection = await getConnection(db_config.name);
    } catch (e) {
      connection = await createConnection(db_config.config);
    }

    // stores connection to selected modules
    for (let module of modules) {
      Reflect.defineMetadata('__tenancyConnection__', connection, module);
    }

    next();
  };
}
Run Code Online (Sandbox Code Playgroud)

我将它添加到 main.ts:

const app = await NestFactory.create(AppModule);
app.use(tenancyConnection(AppModule));
Run Code Online (Sandbox Code Playgroud)

要访问连接,您可以通过以下方式扩展任何服务:

export class TenancyConnection {

  getConnection(): Connection {
    return Reflect.getMetadata('__tenancyConnection__', AppModule);
  }
}
Run Code Online (Sandbox Code Playgroud)

它仍然是一个草稿,但使用此解决方案,您可以在运行时为每个租户添加、删除和编辑连接。我希望这能帮助你进一步。