-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
feat(context): provide resolution context metadata for factory functions with toDynamicValue() #5370
Conversation
7c4e33e
to
8defa1d
Compare
8defa1d
to
3f11e90
Compare
3f11e90
to
3895b22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea 👏 Let's discuss the proposed design, I have also few comments on the implementation details of the benchmarks.
I'd also like to hear from @deepakrkris @emonddr @jannyHo as the CODEOWNERS of the context package.
Let me add more context to explain the ideal I'd like us to eventually reach in the future. Let's take a slightly modified example from the PR description: ctx.bind('user').to('John');
ctx.bind('greeting').toDynamicValue(getUserGreeting);
function getUserGreeting(_ctx) {
const user = _ctx.getSync('user');
return `Hello, ${user}`;
} Using Service Locator has several flaws:
Let's see how Dependency Injection makes everything better:
Here is a mock-up of a possible implementation that I was playing with few years ago: export class Binding<T = BoundValue> extends EventEmitter {
// ...
// no dependencies
toDynamicValue(factoryFn: () => ValueOrPromise<T>): this;
// 1 dependency
toDynamicValue<D1>(key1: BindingKey<D1>, factoryFn: (d1: D1) => ValueOrPromise<T>): this;
// 2 dependencies
toDynamicValue<D1, D2>(key1: BindingKey<D1>, key2: BindingKey<D2>, factoryFn: (d1: D1, d2: D2) => ValueOrPromise<T>): this;
// rinse & repeat for 3,4,5,... dependencies
// the implementation
toDynamicValue(...args: unknown) {
const factoryFn = args.pop() as (...deps: unknown[]) => ValueOrPromise<T>;
const depKeys = args as BindingKey<unknown>[];
this._setValueGetter(ctx => {
const depValues = resolveList(depKeys, k => ctx.getPromiseOrValue(k));
return transformValueOrPromise(depValues, values => factoryFn(values));
});
}
} Now as I wrote before, I am not against the Service Locator approach proposed in this pull request, I am fine to implement it as a short-to-medium-term solution for dynamic value factories. I am just asking to design the APIs in such way that will make it feasible to add Dependency Injection to dynamic value factories later in the future, in a way that won't be disruptive to existing applications using dynamic values based on the Service Locator pattern. |
3895b22
to
48d437c
Compare
@bajtos I think I have a better/lighter DI solution without reinventing the wheels. class StaticGreetingProvider {
static value(@inject('user') user: string) {
return `Hello, ${user}`;
}
}
ctx.bind('greeting').toDynamicValue(StaticGreetingProvider); The idea is to have a class with |
7deabac
to
02e2921
Compare
56b877e
to
32744a5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better 👏
I like the approach using a static value
method to support @inject
based decoration 👍
There is the downside of missing type checks of injected parameters vs. binding key value type, but that's a problem of all existing patterns we have for @inject
, so this PR is not making the situation much worse.
I have few comments on how to improve implementation details, see below.
32744a5
to
c21f214
Compare
@bajtos Comments addressed. PTAL. |
…DynamicValue() A provider class can use dependency injection to receive resolution-related metadata such as `context` and `binding`. But the overhead to wrap a factory function is not desired for some use cases. This PR introduces a lightweight alternative using `toDynamicValue` as follows: 1. Add a parameter to the factory function to receive resolution context 2. Support a specialized class with a static `value` method that allows parameter injection. Some benchmark tests are added to `benchmark` to measure performances for various flavors of the dynamic value resolution for bindings.
c21f214
to
6674925
Compare
A provider class can use dependency injection to receive resolution-related
metadata such as
context
andbinding
. But the overhead to wrap a factoryfunction is not desired for some use cases. This PR introduces a lightweight
alternative using
toDynamicValue
as follows:value
method that allowsparameter injection.
Some benchmark tests are added to
benchmark
to measure performances forvarious flavors of the dynamic value resolution for bindings.
See https://github.com/strongloop/loopback-next/tree/pass-ctx-to-dynamic-value/benchmark/src/context-binding for examples.
This change is backward compatible. Existing factory functions continue to work.
Checklist
👉 Read and sign the CLA (Contributor License Agreement) 👈
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated👉 Check out how to submit a PR 👈