web-dev-qa-db-ja.com

NestJS:リクエストによるデータベース接続(TypeORM)(サブドメイン)

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:私が現在行っていること:

  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();
          };
        }
    }
    
  3. コントローラ内:@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)
          )
        }
    
  4. サービス中:指定されたインスタンスの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();
      }
    

それはうまくいくようですが、私はほとんどすべてについて確信がありません:

  • 接続プール(ConnectionManagerに接続を作成できる数はいくつですか?)
  • サブドメインをresponse.localsに渡します...悪い習慣ですか?
  • 読みやすさ/理解/多くの追加コードの追加...
  • 副作用:いくつかのサブドメイン間で接続を共有することを恐れています
  • 副作用:パフォーマンス

Response.send()+ Promise + await(s)+ passサブドメインをどこでも処理するのは楽しいことではありません...

サブドメインをサービスに直接取得する方法はありますか?

正しいサブドメイン接続/リポジトリをサービスに直接取得して、コントローラーに挿入する方法はありますか?

7
yoh

Yohのソリューションに触発されましたが、NestJSの新機能に合わせて少し調整しました。その結果、コードが少なくなります。

1)DatabaseMiddlewareを作成しました

import { Injectable, NestMiddleware, Inject } from '@nestjs/common';
import { getConnection, createConnection, ConnectionOptions } from "typeorm";

@Injectable()
export class DatabaseMiddleware implements NestMiddleware {

  public static COMPANY_NAME = 'company_name';

  async use(req: any, res: any, next: () => void) {
    const databaseName = req.headers[DatabaseMiddleware.COMPANY_NAME];

    const connection: ConnectionOptions = {
      type: "mysql",
      Host: "localhost",
      port: 3307,
      username: "***",
      password: "***",
      database: databaseName,
      name: databaseName,
      entities: [
        "dist/**/*.entity{.ts,.js}",
        "src/**/*.entity{.ts,.js}"
      ],
      synchronize: false
    };

    try {
      getConnection(connection.name);
    } catch (error) {
      await createConnection(connection);
    }

    next();
  }

}

2)main.tsではすべてのルートに使用します

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(new DatabaseMiddleware().use);
  ...

3)稼働中の取得接続

import { Injectable, Inject } from '@nestjs/common';
import { Repository, getManager } from 'typeorm';
import { MyEntity } from './my-entity.entity';
import { REQUEST } from '@nestjs/core';
import { DatabaseMiddleware } from '../connections';

@Injectable()
export class MyService {
  private repository: Repository<MyEntity>;

  constructor(@Inject(REQUEST) private readonly request) { 
    this.repository = getManager(this.request.headers[DatabaseMiddleware.COMPANY_NAME]).getRepository(MyEntity);
  }

  async findOne(): Promise<MyEntity> {
    return await this.repository
    ...
  }

}
1