Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for request-scoped providers based on CLS #30

Closed
Papooch opened this issue Jun 19, 2022 · 0 comments · Fixed by #45
Closed

Add support for request-scoped providers based on CLS #30

Papooch opened this issue Jun 19, 2022 · 0 comments · Fixed by #45
Labels
enhancement New feature or request
Milestone

Comments

@Papooch
Copy link
Owner

Papooch commented Jun 19, 2022

This is still just an idea, but I think it's very much possible to use a Proxy in combination of CLS to create a truly request-scoped providers in the Nest sense of the word but without the need to recreate the whole provider chain.

The implementation would be is similar to the one of request-scoped beans in the Spring framework for Java.

This would enable the following usage:

@Injectable()
export class UserService {
  constructor(
    // inject a singleton provider, that is actually a Proxy instance with correctly set up accessor traps
    @InjectCls(RequestUser) user: RequestUser
  ) {}

  getCurrentUserName() {
    // when accessing the object, we the call is proxied to the current user object in the CLS
    return this.user.name
  }
}

or even

@Injectable()
export class UserService {
  constructor(
    // it could be possible to inject arbitrary provider - in this case a connection to the tenant database
    @InjectCls('TENANT_CONNECTION') db: Knex
  ) {}

  getCurrentUserFromDb() {
    // the call would be again proxied to the current TENANT_CONNENCTION object in the CLS
    return this.db.select('whatever').from('users') ...
  }
}

The registration of such providers could be done as follows:

imports: [
   ClsModule.forFeature(RequestUser)
]

and If we were to support the second use case - that is arbitrary factory providers, then even

imports: [
  ClsModule.forFeature({
    provide: 'TENANT_CONNENCTION',
    import: [DatabaseModule],
    inject: [ClsService, DatabaseService],
    useFactory: async (cls: ClsService, databaseService: DatabaseService) => {
      const tenantId = cls.get('tenantId');
      const tenantConnection: Knex = await databaseService.getTenantConnection(tenantId);
      return tenantConnection;
    }
  })
]

It would be needed to figure out where and when to populate the CLS store with the provider. It is too late to do it at the proxy access time, because the provider could be itself async so it would have to have been already constructed at the time of access.

Next issue is how to inject providers and such. All this would probably happen in the enhancer when the CLS context is being set up using moduleRef.

Also, what about situations where we're setting up the CLS store manually with cls.run()?. We'd need to construct the providers there somehow and since the operation must be async, it can't be done in cls.run() directly. We'll probably need an explicit call - something like await cls.boostrapProxyProviders().

@Papooch Papooch added the enhancement New feature or request label Jun 19, 2022
@Papooch Papooch added this to the 3.0 milestone Jun 19, 2022
This was referenced Jun 19, 2022
@Papooch Papooch linked a pull request Oct 1, 2022 that will close this issue
This was referenced Oct 1, 2022
@Papooch Papooch linked a pull request Oct 4, 2022 that will close this issue
Papooch added a commit that referenced this issue Oct 4, 2022
* refactor!: remove namespaces support (#36)

*refactor!: rename register method

* feat: add proxy providers (#30)

* feat: add CLS_REQ and CLS_RES proxy providers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant