-
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
Add per-request child context #188
Conversation
Connect to strongloop-internal/scrum-asteroid#117 |
packages/context/src/context.ts
Outdated
this.registry = new Map(); | ||
this.bind('ctx').to(this); |
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.
This is not ideal, see the next comment explaining why I have added this.
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.
Maybe we should do this... (with some way of turning off the warning in the tests...
ctx.bind('ctx').toDynamicValue(() => {
console.warn('Hey... don't use @inject('ctx') !!!');
return ctx;
});
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.
Not a bad idea.
|
||
@api(spec) | ||
class FlagController { | ||
constructor(@inject('ctx') private ctx: Context) { |
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.
This is the test injecting the full context.
Here is the problem I am trying to solve: I need to write a test that fails when the same app context is used for all requests. To detect that, I need one of the following:
-
Some way how to modify a binding in the (per-request) context - that's what I am doing now
-
Use advanced async/timing control ensure that two requests run in parallel are executed in lockstep. Then I can inject the request object to the controller, write a method that returns the request url including the query string, and use a different query string in each request value. When the timing is right, the controller method executed for the first request will receive the second request from
@inject
, and the test fails. This is not possible at the moment, because I have no control over the async flow inside Router/Context/invoke. I think this could be achieved if context supported async bindings.
Can you think of any better way how to test per-request context instances?
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.
I have a holistic proposal for this... I'd call this the Black Hole Problem
- you can bind
at the app
level, and those bindings are inherited now (thanks to this PR), but you cannot rebind per request.
@bajtos and I will have some realtime discussion about this soon and link to the proposal here.
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.
For the purpose of testing new instances, can you cache ctx
to the class and assert the new one is not the same as the cached one?
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.
That's basically what I am doing here. Instead of comparing ctx
references (which yield ugly assertion failures), I bind a value to the per-request context and then verify that the second request does not see the value set by the first request.
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.
The reason why I don't like this approach is that user code should never access the DI container (ctx
) directly, that breaks the whole DependencyInjection/InversionOfControl design.
@@ -11,8 +11,9 @@ export type Constructor<T> = new(...args) => T; | |||
export class Context { | |||
private registry: Map<string, Binding>; | |||
|
|||
constructor() { | |||
constructor(private _parent?: Context) { | |||
this.registry = new Map(); |
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.
Each instance of Context
should have a unique id for tracing purposes.
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.
Interesting idea. I think I could use that unique id in my test instead of @inject('ctx')
.
@raymondfeng How do you propose to generate that unique id? Should we just increase a number, or should we use uuid
?
Do you have any other comments, or does the rest of the patch look good to you?
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.
I think uuid
is good enough. We should also check out http://opentracing.io/documentation/.
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.
On the second thought, let's move the unique tracing id out of scope of this patch - see #209
// TODO(bajtos) Create a new nested/child per-request Context | ||
const requestContext = this; | ||
const requestContext = new Context(this); | ||
requestContext.bind('http.request').to(req); |
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.
I wonder if we should use an even more qualified name as transport.http.request
. Such namespace/package can help us organize the context. Food for thoughts.
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.
I think we should qualify it when we actually implement other protocols.
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.
LGTM with some suggestions.
// TODO(bajtos) Create a new nested/child per-request Context | ||
const requestContext = this; | ||
const requestContext = new Context(this); | ||
requestContext.bind('http.request').to(req); |
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.
I think we should qualify it when we actually implement other protocols.
Let's land async context bindings via #193 before continuing the work on this patch. |
f733e49
to
bd4eb80
Compare
Instead of creating a binding that should not be used, and adding even more code to allow tests to suppress the warning, I found a solution that I consider much more elegant: create this special app.bind('context').getValue = ctx => ctx; |
bd4eb80
to
14b48ec
Compare
Create a new child context for each request handled by the app. Add the following two bindings to this new context:
http.request
http.response
This is a pre-requisite for #186 which I am implementing as part of the time allocated in https://github.com/strongloop-internal/scrum-asteroid/issues/117.
cc @bajtos @raymondfeng @ritch @superkhau