我正在尝试通过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文件
  • 在所有路由上创建中间件,以将子域注入(inject)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();
          };
        }
    }
    
  • 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)
          )
        }
    
  • Service中的
  • :通过存储库获取给定实例的TypeORM管理器并查询数据库
    @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();
      }
    

  • 它似乎可行,但我不确定几乎所有内容:
  • 连接池(我可以在ConnectionManager中创建多少个连接?)
  • 将子域传递到response.locals ...不好的做法?
  • 可读性/理解力/添加许多其他代码...
  • 副作用:恐怕要在几个子域之间共享连接
  • 副作用:性能

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

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

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

    最佳答案

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

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

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

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

    关于typeorm - NestJS : database connection (TypeORM) by request (subdomain),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/51385738/

    10-09 17:48