-
Notifications
You must be signed in to change notification settings - Fork 2k
Scoped Context #2692
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
Comments
I can see the value in adding this as a feature, although it'd need to be configurable. Scoping context this way would require cloning the context object repeatedly, which would result in additional overhead. It would also prevent being able to pass down some object that'd be mutated during execution, like a |
In order to decouple it from the main context object, without knowing the inner workings of the client, my initial thought was to create some sort of object where each change to the context would be additive, and then the resulting “scoped context” object inside the children resolvers would just be a merged combination of the contexts set by all of the parent resolvers (in a quick and dirty middleware approach I would use the path field in the resolver info to identify the ancestry). It might be worth considering separating the scoped context away from the context as it is today completely in order to avoid issues you described where mutations to the context need to be shared globally across all resolvers, and so cloning wouldn’t have to be done (causing the performance overhead). I’m planning on making a prototype middleware (which might be sufficient as your on-off switch, just include the middleware) in the next few weeks, that just injects this “scoped context manager” inside the main context object that does exactly as I described above. Individual contributions would be tracked with their path and the resulting (additive) context would be built for each resolver requesting it by looking at the path of the current resolver and looking for entries to the scoped context with paths that indicate ancestry to the current resolver. |
GraphQL-Java has this feature, which they call local context. https://www.graphql-java.com/blog/deep-dive-data-fetcher-results/ is an explanation of how it works. Basically it's a mechanism that allows you to use your application's model types directly for the "parent" objects rather than having to encode contextual data in the "parent" objects. |
The errors argument would probably simplify schema proxying/stitching as well. |
FYI graphql-ruby seems to support this too rmosolgo/graphql-ruby#2634. |
As a "simple" workaround you can do this by using a map in the context where each key represents a specific entity (eg |
By implementing the ScopedContext marker interface in your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` For now I decided to use a marker interface `ScopedContext`. Although I'm not fan of marker interfaces, it was the easiest way to get this working. We could also make this an option that needs to be passed to the `Executor`. Something like `$useScopedContext = false` (disabled by default). References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext marker interface in your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` For now I decided to use a marker interface `ScopedContext`. Although I'm not fan of marker interfaces, it was the easiest way to get this working. We could also make this an option that needs to be passed to the `Executor`. Something like `$useScopedContext = false` (disabled by default). References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext marker interface in your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` For now I decided to use a marker interface `ScopedContext`. Although I'm not fan of marker interfaces, it was the easiest way to get this working. We could also make this an option that needs to be passed to the `Executor`. Something like `$useScopedContext = false` (disabled by default). References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext marker interface in your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` For now I decided to use a marker interface `ScopedContext`. Although I'm not fan of marker interfaces, it was the easiest way to get this working. We could also make this an option that needs to be passed to the `Executor`. Something like `$useScopedContext = false` (disabled by default). References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext marker interface in your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` For now I decided to use a marker interface `ScopedContext`. Although I'm not fan of marker interfaces, it was the easiest way to get this working. We could also make this an option that needs to be passed to the `Executor`. Something like `$useScopedContext = false` (disabled by default). References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `d` * `f` receives the cloned context from `d` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `a` * `f` receives the cloned context from `e` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
By implementing the ScopedContext interface on your context object, you can ensure that scope is only passed downwards and not shared with other fields. Example: ```graphql query { a { b { c } d { e { f } } } g { h { i } } } ``` In the above situation, the following will happen: * `a` receives the cloned context from the executor * `b` receives the cloned context from `a` * `c` receives the cloned context from `b` * `d` receives the cloned context from `a` * `f` receives the cloned context from `e` * `g` receives the cloned context from the executor * `h` receives the cloned context from `g` * `i` receives the cloned context from `h` References: graphql/graphql-js#2692 rmosolgo/graphql-ruby#2634
Apologies if this is meant to go with the specification instead of this specific client, but I couldn't find much reference to the concept of context inside the official specification, so assumed it was a client-specific implementation detail.
With that said, a .NET GraphQL client has the following feature: Scoped Context Data
I've seen a few issues revolving around this idea (this one in particular comes closest), but I haven't seen any issues call out the functionality explicitly.
I think the documentation from Hot Chocolate explains it best:
Since I'm familiar with React, I also like to think of it as React.Context, but applied to GraphQL.
Arguably, I would assume the general suggestion for this kind of problem would be something along the lines of "adjust your architecture," but I still do believe the feature has some benefit once you get into some advanced schemas.
Another Use-Case
If you would like another use-case, the one I am running into specifically goes as follows:
Comment
types, each containing the user that posted the comment.Comment
types lives on aMedia
type, which represents a general piece of media and is used in several places throughout my service.The solution, in this case, is to have the parent resolver set a flag in the context that causes the Comment resolver way down stream to hide the associated user.
The text was updated successfully, but these errors were encountered: