Search code examples
nestjsnestjs-confignestjs-microservice

Avoiding Duplicate Dependency Resolution When Creating Multiple Microservices in NestJS


In a NestJS project, I'm facing a challenge with duplicate dependency resolution when creating multiple microservices. The standard approach to initializing multiple microservices seems to re-instantiate dependencies for each one. How I can get rid of that?

Of course, I can use amqplib or something similar, but then @MessagePattern({cmd:"something"}) and other native NestJS code won't work.

The same problem would be if you want to get some config from ConfigModule parameter in order to instantiate microservice. Because to resolve dependency of ConfigModule you need to create app instance first and then again instance for microservice.

async function bootstrap() {
  const appContext = await NestFactory.createApplicationContext(AppModule);
  // OR: const appContext = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule);
  const configService = appContext.get(ConfigService);
  const microserviceOptions: MicroserviceOptions = {
    transport: Transport.RMQ,
    options: {
      urls: [configService.get<string>(ConfigPropertyNames.RABBITMQ_URI)],
      queue: QueueNames.AUTH_SERVICE_RPC,
      queueOptions: {
        durable: false
      },
    },
  }
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, microserviceOptions);

  await app.listen();
  Logger.log(
    `🚀 Application is running`
  );
}
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [NestFactory] Starting Nest application...
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] AppModule dependencies initialized +13ms
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] CustomConfigModule dependencies initialized +1ms
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] MongooseModule dependencies initialized +0ms
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 23400  - 14.03.2024, 10:51:58     LOG [InstanceLoader] LoggerModule dependencies initialized +69ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongooseCoreModule dependencies initialized +349ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongooseModule dependencies initialized +6ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongoDBModule dependencies initialized +1ms
2024-03-14 10:51:59 INFO : User service is being initialized
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] AuthModule dependencies initialized +2ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [NestFactory] Starting Nest application... +2ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] AppModule dependencies initialized +3ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] CustomConfigModule dependencies initialized +0ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongooseModule dependencies initialized +1ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] LoggerModule dependencies initialized +3ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongooseCoreModule dependencies initialized +303ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongooseModule dependencies initialized +1ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] MongoDBModule dependencies initialized +0ms
2024-03-14 10:51:59 INFO : User service is being initialized
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [InstanceLoader] AuthModule dependencies initialized +1ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG [NestMicroservice] Nest microservice successfully started +357ms
[Nest] 23400  - 14.03.2024, 10:51:59     LOG 🚀 Application is running

Solution

  • I think you could take advantage of Hybrid application support from NestJS.
    Your code would become:

    const app = await NestFactory.create(AppModule);
    const configService = appContext.get(ConfigService);
    const microserviceOptions: MicroserviceOptions = {
        transport: Transport.RMQ,
        options: {
          urls: [configService.get<string>(ConfigPropertyNames.RABBITMQ_URI)],
          queue: QueueNames.AUTH_SERVICE_RPC,
          queueOptions: {
            durable: false
          },
        },
        inheritAppConfig: true // Note this extra config
    }
    const microservice = app.connectMicroservice<MicroserviceOptions>(microserviceOptions);
    
    await app.startAllMicroservices();
    await app.listen(3000);
    

    Otherwise, recently NestJS has introduced Conditional modules registration. You could create a MicroserviceModule, move all the interested modules inside of it, and register it based on a ConfigService variable (e.g. isMicroservice). You could use two different environment files for the two application but it can be tricky.

    @Module({
      imports: [ConfigModule.forRoot(), ConditionalModule.registerWhen(MicroserviceModule, 'USE_MICROSERVICE')],
    })
    export class AppModule {}
    

    Finally, the simplest way would be to create a "dummy" AppModule that has only the ConfigService as imports, create app from it, and create microservice with your real AppModule

    @Module({
      imports: [ConfigModule.forRoot(),
    })
    export class AppConfigModule {}
    @Module({
    ...yourAppModuleConfig
    })
    export class AppModule {}
    
    async function bootstrap() {
      const appContext = await NestFactory.createApplicationContext(AppConfigModule);
      // OR: const appContext = await NestFactory.createMicroservice<MicroserviceOptions>(AppConfigModule);
      const configService = appContext.get(ConfigService);
      const microserviceOptions: MicroserviceOptions = {
        transport: Transport.RMQ,
        options: {
          urls: [configService.get<string>(ConfigPropertyNames.RABBITMQ_URI)],
          queue: QueueNames.AUTH_SERVICE_RPC,
          queueOptions: {
            durable: false
          },
        },
      }
      const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, microserviceOptions);
    
      await app.listen();
      Logger.log(
        `🚀 Application is running`
      );
    }