From 2f2252264bff45e383488252c838b0ab55a49a25 Mon Sep 17 00:00:00 2001 From: Jay McDoniel Date: Fri, 20 Oct 2023 14:21:33 -0700 Subject: [PATCH] feat: allow for microservice options to come from the di container Microservices are now able to be created by getting their options from within the DI container itself. This has been a long requested feature of developers and I finally had some time to work through how we could possibly let this happen. --- .../microservices/e2e/sum-rpc-async.spec.ts | 98 +++++++++++++++++++ .../microservice-configuration.interface.ts | 12 ++- packages/microservices/nest-microservice.ts | 28 ++++-- 3 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 integration/microservices/e2e/sum-rpc-async.spec.ts diff --git a/integration/microservices/e2e/sum-rpc-async.spec.ts b/integration/microservices/e2e/sum-rpc-async.spec.ts new file mode 100644 index 000000000..45ed1e5cc --- /dev/null +++ b/integration/microservices/e2e/sum-rpc-async.spec.ts @@ -0,0 +1,98 @@ +import { + Controller, + INestMicroservice, + Injectable, + Module, +} from '@nestjs/common'; +import { + AsyncOptions, + ClientTCP, + ClientsModule, + MessagePattern, + MicroserviceOptions, + Payload, + TcpClientOptions, + Transport, +} from '@nestjs/microservices'; +import { expect } from 'chai'; +import { NestFactory } from '@nestjs/core'; + +let port: number; + +do { + port = Math.round(Math.random() * 10000); +} while (port < 1000); + +@Injectable() +class RpcOptionsProvider { + getOptions(): TcpClientOptions { + return { + transport: Transport.TCP, + options: { + port, + host: '0.0.0.0', + }, + }; + } +} + +@Controller() +class RpcController { + @MessagePattern({ cmd: 'sum' }) + sumPayload(@Payload() payload: number[]) { + return payload.reduce((a, b) => a + b, 0); + } +} + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'RPC_CLIENT', + transport: Transport.TCP, + options: { + port, + host: '0.0.0.0', + }, + }, + ]), + ], + controllers: [RpcController], + providers: [RpcOptionsProvider], +}) +class RpcModule {} + +describe('RPC Async transport', () => { + let app: INestMicroservice; + let client: ClientTCP; + + beforeEach(async () => { + app = await NestFactory.createMicroservice< + AsyncOptions + >(RpcModule, { + logger: false, + inject: [RpcOptionsProvider], + useFactory: (optionsProvider: RpcOptionsProvider) => + optionsProvider.getOptions(), + }); + + await app.listen(); + client = app.get('RPC_CLIENT', { strict: false }); + }); + + it(`/POST`, done => { + let retData = 0; + client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]).subscribe({ + next: val => (retData += val), + error: done, + complete: () => { + expect(retData).to.eq(15); + done(); + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/packages/microservices/interfaces/microservice-configuration.interface.ts b/packages/microservices/interfaces/microservice-configuration.interface.ts index 06bf7433d..8c6cbf0e6 100644 --- a/packages/microservices/interfaces/microservice-configuration.interface.ts +++ b/packages/microservices/interfaces/microservice-configuration.interface.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { FactoryProvider, InjectionToken, Type } from '@nestjs/common'; import { ConnectionOptions } from 'tls'; import { Transport } from '../enums/transport.enum'; import { ChannelOptions } from '../external/grpc-options.interface'; @@ -28,6 +28,16 @@ export type MicroserviceOptions = | KafkaOptions | CustomStrategy; +export type AsyncMicroserviceOptions = { + inject: InjectionToken[]; + useFactory: (...args: any[]) => MicroserviceOptions; +}; + +export type AsyncOptions = { + inject: InjectionToken[]; + useFactory: (...args: any[]) => T; +}; + /** * @publicApi */ diff --git a/packages/microservices/nest-microservice.ts b/packages/microservices/nest-microservice.ts index 7b0930e69..fd9c7aee6 100644 --- a/packages/microservices/nest-microservice.ts +++ b/packages/microservices/nest-microservice.ts @@ -17,7 +17,10 @@ import { GraphInspector } from '@nestjs/core/inspector/graph-inspector'; import { NestApplicationContext } from '@nestjs/core/nest-application-context'; import { Transport } from './enums/transport.enum'; import { CustomTransportStrategy } from './interfaces/custom-transport-strategy.interface'; -import { MicroserviceOptions } from './interfaces/microservice-configuration.interface'; +import { + AsyncMicroserviceOptions, + MicroserviceOptions, +} from './interfaces/microservice-configuration.interface'; import { MicroservicesModule } from './microservices-module'; import { Server } from './server/server'; import { ServerFactory } from './server/server-factory'; @@ -43,7 +46,8 @@ export class NestMicroservice constructor( container: NestContainer, - config: NestMicroserviceOptions & MicroserviceOptions = {}, + config: NestMicroserviceOptions & + (MicroserviceOptions | AsyncMicroserviceOptions) = {}, private readonly graphInspector: GraphInspector, private readonly applicationConfig: ApplicationConfig, ) { @@ -60,12 +64,22 @@ export class NestMicroservice this.selectContextModule(); } - public createServer(config: NestMicroserviceOptions & MicroserviceOptions) { + public createServer( + config: NestMicroserviceOptions & + (MicroserviceOptions | AsyncMicroserviceOptions), + ) { try { - this.microserviceConfig = { - transport: Transport.TCP, - ...config, - } as any; + if ('useFactory' in config) { + const args = config.inject?.map(token => + this.get(token, { strict: false }), + ); + this.microserviceConfig = config.useFactory(...args); + } else { + this.microserviceConfig = { + transport: Transport.TCP, + ...config, + } as any; + } const { strategy } = config as any; this.server = strategy ? strategy