loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.95k stars 1.07k forks source link

Injection-specific contexts #4452

Closed bradharms closed 3 years ago

bradharms commented 4 years ago

Suggestion

This is more of a discussion than a direct suggestion.

I want to talk about the possibility of an enhancement of the injection system that makes it easier to allow a new child context to be created specific to a particular injection point, using a callback to modify the injection-specific context.

Use Cases

The primary use case for this is to override and/or extend configurations for the services that the injecting service would consume. For example, if service A consumes service B, and B consumes service C, and service C can be configured by service C.$config, then A should be able to bind a new C.$config that will only apply within the context of A's injection of B (and thus B's injection of C) and at nowhere else in the service stack. So the version of C that exists within A's injection of B would get this new config, but no other version of C would.

(NOTE: This would imply that services A, B, and C all need to be bound in the CONTEXT scope.)

It would also be possible to swap out services with dummy versions of those services in order to disable them. For example, if C above were a security check function which returns a boolean to indicate access rights, C itself could be re-bound within A's injection of B to a function that always returns true, thus effectively bypassing any security checks performed by B in this localized context.

It's actually technically possible to do what I'm describing already using the resolve parameter of the @inject decorator. This proposal is more about cleaning up, standardizing, and documenting the technique somehow. (It's also possible that what I'm describing is already covered in documentation somewhere and I just missed it; if so, please feel free to redirect this thread there and close the thread.)

Examples

Here is how it might be done if we were to limit the new API to only using it for simple configuration overrides:

class A {
  constructor(
    @injectWithConfig(KEY_B, {
        [KEY_C]: {
            disableSecurityChecks: true,
        }
    }) private b: B
  ) { }
}

This would be equivalent to the currently-possible code below:

class A {
  constructor(
    @inject('', undefined, async (ctx) => {
      const childCtx = new Context(ctx);
      childCtx.configure(KEY_C).to({
        disableSecurityChecks: true,
      });
      return await childCtx.get(KEY_B);
    }) private b: B
  ) { }
}

Acceptance criteria

TBD - will be filled by the team.

raymondfeng commented 4 years ago

@bradharms Have you checked out two examples below?

raymondfeng commented 4 years ago

@bradharms Did you have a chance to check out the examples? I think they should resolve your use case. Please let us know so that we can either close this issue or have more discussions.

stale[bot] commented 3 years ago

This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository. This issue will be closed within 30 days of being stale.

bradharms commented 3 years ago

Sorry for the long delay in response to this. We were using our in-house solution and I kind of forgot about this issue until now, as recent changes in Loopback's API have brought it to the surface again.

@raymondfeng After reviewing the examples provided I don't think they address the issue I'm describing. The examples shown seem to refer specifically to injection of the configurations themselves. My concern is about injecting services which are constructed with the configuration, or which were constructed with other dependencies that may need to be swapped out with a specific replacement for a particular injection point.

By binding services in BindingScope.CONTEXT it is possible to create a new context in the injection resolver, modify or replace services in the new context, and then resolve the current injection from the modified context. This results in all services bound in BindingScope.CONTEXT which the currently-resolving service depends on having been reconstructed using the modified values.

Note, however, that its no longer safe to use this technique because BindingScope.CONTEXT has been deprecated as of this commit. (Replacing BindingScope.CONTEXT with BindingScope.REQUEST does NOT work.)

stale[bot] commented 3 years ago

This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository. This issue will be closed within 30 days of being stale.

stale[bot] commented 3 years ago

This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.