#How to inject a different instance of a module every time it is imported?

54 messages · Page 1 of 1 (latest)

tawdry spindle
#

I have a dynamic module which creates a connection with the redis database. When I import the module in two different modules with different database numbers it is still using the same instance in both with database number 0.

I need it create a separate connection every time I import the module.

blissful kayak
tawdry spindle
#

The service injects the connection from the module like so:

import { Injectable, Scope } from '@nestjs/common';
import { RedisDataSource } from './RedisDatasource';

@Injectable({
    scope: Scope.TRANSIENT,
})
export class CacheService {
    constructor(private dataSource: RedisDataSource) {}

    async get(key: string) {
        await this.dataSource.client.get(key);
    }

    async set(key: string, value: any) {
        await this.dataSource.client.set(key, value);
    }
}
#

Yes, I had tried that, since the RedisDataSource I inject is provided by the module, I guess it thus using the same instance.

#

It is provided in the module like so:

providers: [
                {
                    provide: REDIS_CONNECTIONS_OPTIONS_TOKEN,
                    useFactory: moduleOptions.useFactory,
                    inject: moduleOptions.inject,
                    scope: Scope.TRANSIENT,
                },
                {
                    provide: RedisDataSource,
                    useFactory: async (options: RedisClientOptions) => {
                        const dataSource = new RedisDataSource(options);
                        await dataSource.connect();
                        return dataSource;
                    },
                    inject: [REDIS_CONNECTIONS_OPTIONS_TOKEN],
                    scope: Scope.TRANSIENT,
                },
                CacheService,
            ],

TRANSIENT scope does not work here either.

#

But when I see the redis connection instance in the debugger, it shows database as 0, in both the modules.

tawdry spindle
#

INQUIRER in the CacheService returns undefined, I think it is being created in the default scope.

blissful kayak
#

can you show us exactly how you're importing that dynamic module twice?

#

each module that had imported the dynamic one will receive a brand new instance of RedisDataSource if you have that scope: Scope.TRANSIENT added to RedisDataSource
And since CacheService is injecting RedisDataSource, it will becomen transient as well automatically

tawdry spindle
#

I'm importing it like this

CacheModule.registerAsync({
            imports: [ConfigsModule],
            useFactory: (configService: ConfigService) => {
                return {
                    socket: {
                        host: configService.getOrThrow('REDIS.HOST'),
                        port: configService.getOrThrow('REDIS.PORT'),
                    },
                    database: DATABASE_NUMBER,
                };
            },
            inject: [ConfigService],
        }),

just changing the DATABASE_NUMBER in each of the module which imports

blissful kayak
#

so there is no way that DATABASE_NUMBER is the same on each call, right?

tawdry spindle
#

no, it is a constant which is declared in the module file itself and is not exported from it.

blissful kayak
#

I got this when using scope: Scope.TRANSIENT in a custom provider that was injected into a provider with no explicit scope defined (called Foo)

#

then I got this when using @Injectable({ scope: Scope.TRANSIENT })

#

and this is expected, as the docs says

#

I just read

#

now I'll see if that works the same when using factory providers

tawdry spindle
#

Okay, so I am supposed to get the same RedisDataSource instance even if scope is marked TRANSIENT?

tawdry spindle
#

I have marked every dependency as TRANSIENT, even the cache service, do I have to mark the services which use cache service as TRANSIENT too?

blissful kayak
#

I guess this a bug

#

using useValue, it works as I expect

#

but switching to useFactory, it doesn't 🤔

#

I'll checkout old versions of @nestjs/core to see when this has been changed
But feels really weird to me

blissful kayak
tawdry spindle
#

yes, I'm using grpc microservice and it is breaking if I mark the controllers and services which use cache service as TRANSIENT

blissful kayak
#

instead, if you really want one exclusive instance for each .register() call, you can hacky it by adding some sort of random ID to the object returned by .register()

#

for example:

blissful kayak
tawdry spindle
#

nope it still behaves the same way:

static registerAsync(moduleOptions: CacheModuleOptions): DynamicModule {
        return {
            module: CacheModule,
            imports: moduleOptions.imports,
            providers: [
                {
                    provide: REDIS_CONNECTIONS_OPTIONS_TOKEN,
                    useFactory: moduleOptions.useFactory,
                    inject: moduleOptions.inject,
                    // scope: Scope.TRANSIENT,
                },
                {
                    provide: RedisDataSource,
                    useFactory: async (options: RedisClientOptions) => {
                        const dataSource = new RedisDataSource(options, uuid());
                        await dataSource.connect();
                        this.redisDataSource = dataSource;
                        return dataSource;
                    },
                    inject: [REDIS_CONNECTIONS_OPTIONS_TOKEN],
                    // scope: Scope.TRANSIENT,
                },
                CacheService,
            ],
            exports: [CacheService],
        };
    }

I passed a uuid and made it a property in the object called connectionId

#

It is getting logged from both the services and has the same database number

#

see connection id it is not changing

blissful kayak
blissful kayak
tawdry spindle
#

only once

#

inject connection id using a different provider token?

blissful kayak
#

Add a “noop” provider using useValue with some unique random value

#

This is enough to tell to Nest that that dynamic module is different on each call of .register

tawdry spindle
#

oh okay

#

yes, that works

#

can you explain a little bit why it is behaving this way?

blissful kayak
tawdry spindle
#

will you please update here the github issue or you findings if it not much overhead, I'd like to know more about this. Thanks for the help

blissful kayak
#

But if I recall correctly the main issue here is that there is a caching mechanism that rely on the serialized values. And factory providers are ignored (for some intended reason). Which ends up making both of your .register calls looking the same to nestjs DI

tawdry spindle
#

yes, I'm using redis and it creates databases as numbers, so when connecting you have to pass a number to create a database, the provider returning the connection was being used once, even though the database number is different in the useFactory declared by the importing modules

#

And I'm using dynamic modules

blissful kayak
#

but I'll open a new issue on github tomorrow to see Kamil's thoughts and conclusion on this because if this is something that we won't support any time soon, it should be mentioned in the docs somewhere