手动实例化 Nest.js 提供程序的正确方法

Proper way to manually instantiate Nest.js providers

我想我可能误解了 Nest.js 的 IoC 容器,或者整个 DI。

我有一个 class、JSONDatabase,我想根据一些配置值(可以是 JSON 或 SQL)实例化自己。

我的 DatabaseService 提供商:

  constructor(common: CommonService, logger: LoggerService) {

    // eslint-disable-next-line prettier/prettier
    const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;

    if (databaseType === DatabaseType.JSON) {
      this.loadDatabase<JSONDatabase>(new JSONDatabase());
    } else if (databaseType === DatabaseType.SQL) {
      this.loadDatabase<SQLDatabase>(new SQLDatabase());
    } else {
      logger.error('Unknown database type.');
    }
  }

我的JSONDatabaseclass:

export class JSONDatabase implements IDatabase {
  dbType = DatabaseType.JSON;
  constructor(logger: LoggerService, io: IOService) {
    logger.log(`Doing something...`)
  }
}

但是,问题是如果我想让我的 JSONDatabase 利用注入,即。它需要 IOServiceLoggerService,我需要从 DatabaseService 构造函数添加参数,而不是通过 Nest 的 IoC 容器注入它们。

Expected 2 arguments, but got 0 [ts(2554)]
json.database.ts(7, 15): An argument for 'logger' was not provided.

这是执行此操作的正确方法吗?我觉得手动传递这些引用是不正确的,我应该使用 Nest 的自定义提供程序,但是,我不太了解关于这个主题的 Nest 文档。我基本上希望能够 new JSONDatabase() 而不必将引用传递给构造函数并让 Nest.js IoC 容器已经注入现有的单例(运行时依赖注入?)。

我在这里的想法可能完全偏离了基础,但我并没有那么多地使用 Nest,所以我主要是凭直觉工作。感谢您的帮助。

您现在遇到的问题是因为您在调用 new JSONDatabase() 时手动实例化 JSONDatabase,而不是利用 NestJS 提供的 DI。由于构造函数需要 2 个参数(LoggerService 和 IOService)并且您提供 none,它失败并显示消息

Expected 2 arguments, but got 0 [ts(2554)]

我认为根据您的用例,您可以尝试几个不同的选项

  1. 如果您在启动时获取配置并在应用程序生命周期中设置一次数据库,您可以使用具有 useFactory 语法的自定义提供程序。
const providers = [
  {
    provide: DatabaseService,
    useFactory: (logger: LoggerService, io: IOService, config: YourConfigService): IDatabase => {
      if (config.databaseType === DatabaseType.JSON) {
          return new JSONDatabase(logger, io);
      } else if (databaseType === DatabaseType.SQL) {
          return new SQLDatabase(logger, io);
      } else {
        logger.error('Unknown database type.');
      }
    },
    inject: [LoggerService, IOService, YourConfigService]
  },
];

@Module({
  providers,
  exports: providers
})
export class YourModule {}

如果您有 LoggerServiceIOServiceYourConfigurationService 注释 @Injectable() NestJS 会将它们注入到 useFactory 上下文中。您可以在那里检查数据库类型并手动实例化正确的 IDatabase 实现。这种方法的缺点是您无法在运行时轻松更改数据库。 (这可能适合您的用例)

  1. 您可以使用 strategy/factory 模式根据类型获得正确的实现。假设您有一种方法可以根据特定参数保存到不同的数据库。
@Injectable()
export class SomeService {
  constructor(private readonly databaseFactory: DatabaseFactory){}

  method(objectToSave: Object, type: DatabaseType) {
    databaseFactory.getService(type).save(objectToSave);
  }
}


@Injectable()
export class DatabaseFactory {

  constructor(private readonly moduleRef: ModuleRef) {}

  getService(type: DatabaseType): IDatabase {
    this.moduleRef.get(`${type}Database`);
  }
}

上面代码的思路是,根据数据库类型,从 NestJS 作用域中获取正确的单例。这样,如果您愿意,可以很容易地添加一个新数据库——只需添加一个新类型及其实现即可。 (并且您的代码可以同时处理多个数据库!)

  1. 我也相信你可以简单地将已经注入的 LoggerServiceIOService 传递给你手动创建的 DatabasesService (你需要添加 IOService 作为依赖共 DatabaseServce
@Injectable()
export class DatabaseService {
  constructor(common: CommonService, logger: LoggerService, ioService: IOService) {

    // eslint-disable-next-line prettier/prettier
    const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;

    if (databaseType === DatabaseType.JSON) {
      this.loadDatabase<JSONDatabase>(new JSONDatabase(logger, ioService));
    } else if (databaseType === DatabaseType.SQL) {
      this.loadDatabase<SQLDatabase>(new SQLDatabase(logger, ioService));
    } else {
      logger.error('Unknown database type.');
    }
  }
}