首页 文章

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

提问于
浏览
0

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

customer1.domain.com => connect to customer1 database
customer2.domain.com => connect to customer2 database
x.domain.com => connect to x database

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

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


WIP:我目前在做什么:

  • 将所有连接设置添加到ormconfig文件中

  • 在所有路由上创建中间件以将子域注入 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();
      };
    }
}

控制器中的

  • :从@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)
      )
    }
  • in Service:通过Repository获取给定实例和查询数据库的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();
  }

它似乎工作,但我不确定几乎所有的东西:

  • 连接poole(我可以在ConnectionManager中创建多少连接?)

  • 将子域传递给response.locals ...不好的做法?

  • 可读性/理解/添加大量额外代码......

  • 副作用:我害怕在几个子域之间共享连接

  • 副作用:表现

处理response.send()Promise await(s)在任何地方传递子域名都不是很高兴...

有没有办法直接将子域名放入我的服务?

有没有办法将正确的子域连接/存储库直接存入我的服务并将其注入我的控制器?

1 回答

  • 1

    我提出了另一个解决方案 .

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

    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();
      };
    }
    

    我把它添加到main.ts:

    const app = await NestFactory.create(AppModule);
    app.use(tenancyConnection(AppModule));
    

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

    export class TenancyConnection {
    
      getConnection(): Connection {
        return Reflect.getMetadata('__tenancyConnection__', AppModule);
      }
    }
    

    它仍然是草稿,但使用此解决方案,您可以在运行时为每个租户添加,删除和编辑连接 . 我希望这会对你有所帮助 .

相关问题