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 per-request child context #188

Merged
merged 1 commit into from
Apr 27, 2017
Merged

Add per-request child context #188

merged 1 commit into from
Apr 27, 2017

Conversation

bajtos
Copy link
Member

@bajtos bajtos commented Apr 21, 2017

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

@bajtos bajtos added this to the Sprint 34 - Asteroid milestone Apr 21, 2017
@bajtos bajtos self-assigned this Apr 21, 2017
@bajtos bajtos added the review label Apr 21, 2017
@bajtos
Copy link
Member Author

bajtos commented Apr 21, 2017

Connect to strongloop-internal/scrum-asteroid#117

this.registry = new Map();
this.bind('ctx').to(this);
Copy link
Member Author

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.

Copy link
Contributor

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;
});

Copy link
Contributor

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) {
Copy link
Member Author

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:

  1. Some way how to modify a binding in the (per-request) context - that's what I am doing now

  2. 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?

Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member Author

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();
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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/.

Copy link
Member Author

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);
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

@raymondfeng raymondfeng left a 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);
Copy link
Contributor

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.

@bajtos
Copy link
Member Author

bajtos commented Apr 24, 2017

Let's land async context bindings via #193 before continuing the work on this patch.

@bajtos bajtos force-pushed the feature/per-request-context branch from f733e49 to bd4eb80 Compare April 27, 2017 12:26
@bajtos
Copy link
Member Author

bajtos commented Apr 27, 2017

this.bind('ctx').to(this)

@bajtos
This is not ideal, see the next comment explaining why I have added this.

@ritch
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;
});

@raymondfeng
Not a bad idea.

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 context binding directly inside the test.

app.bind('context').getValue = ctx => ctx;

@bajtos bajtos force-pushed the feature/per-request-context branch from bd4eb80 to 14b48ec Compare April 27, 2017 13:00
@bajtos bajtos merged commit a422dfb into master Apr 27, 2017
@bajtos bajtos deleted the feature/per-request-context branch April 27, 2017 13:15
@bajtos bajtos removed the review label Apr 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants