-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
I have been spending a lot of time trying to move the existing execution code forward in a compatible way, but I'm finding it impossible :S I mean at least these modules:GraphQL::Execution::Interpreter, Interpreter::Runtime, Interpreter::Arguments (and HasArguments, ArgumentValue), Dataloader, Execution::Lazy.
Writing up all the details is possible but in the interest of getting started, I'll be brief:
- Performance: A lot of possible performance gains have been identified (Memoize selection gathering #5502, Supporting Batch Resolvers in GraphQL-Ruby #5446, Don't check every returned object for
lazy?#4426) but are stuck in compatibility land for edge cases - Correctness: GraphQL-Ruby has received a lot of features over the years, tacked on in different ways. They don't always work right together... it's worth considering which ones can be jettisoned for other approaches (field
extras?) or merged together, at least under the hood (prepare:,loads:,validates:?). - Features: actually, performance is the feature that I'd want from this. I expect most features could be reimplemented one way or another -- or at least a very clear migration path -- with a new core runtime.
So, I'm proposing a project like:
- Build a new runtime in isolation (takes the same inputs as
Schema.run, returns the same output; can be used on a query-by-query basis for Scientist-like checks) - Make sure the new runtime checks the necessary boxes (see below)
- One at a time, reimplement existing features to the degree possible
- Also build a "GraphQL Doctor" which inspects source code and schemas in memory and outputs the result of compatibility checks and links to migration notes
After trying and trying, I don't think a future execution paradigm can be achieved with a perfectly smooth path, but I think it's worth pursing -- Shopify has been using custom execution code to great success for a while now.
New Execution Goals in no specific order:
- Support first-class batching ("batch resolvers"), so you can get the advantages of dataloader or graphql-batch without promises or fibers, using a breadth-first execution algorithm (Supporting Batch Resolvers in GraphQL-Ruby #5446)
- Non-recursive, non-block-based execution algorithm to reduce overhead (Use a queue for execution #5389)
- Properly cache merged AST nodes (Memoize selection gathering #5502)
- Others?
cc @gmac since you've been investigating a lot of these too -- what do you think this approach to improving GraphQL-Ruby?
Appendix: essential and non-essential forwards-compatible features
These are things that must have good enough compatibility that a given application can switch between runtimes on a query-by-query basis:
- Everything in the GraphQL spec (including null propagation,
@skip/@include, static validation (same code)) - Query analyzers (same code used in both cases)
- Custom scalars
- Resolver classes
- Dataloader
- GraphQL-Batch support
- Schema visibility (same code should work in this new runtime, since it's applied during static validation)
- Multiplex: I expect this to basically work fine in the new flow with a bit of work.
- ... I will add other things here as I encounter them
Then there are some features whose forward compatibility I don't consider to be essential. In these cases, supporting both runtimes in your schema might require de-customizing (ie, moving behavior into your app code or into a resolver). However I bet they'll eventually be able to be reimplemented in a very compatible way, and I expect them to be easily identifiable by "GraphQL Doctor":
- Argument
loads:,prepare:,validates: - Field
extras,extensions - Custom context and query classes
- GraphQL::Schema::Object
.wrap(...),.authorized?: I expect the object lifecycle to change here. At a minimum, it will be required to opt intoYourObjectType.newever being called. There will definitely be some future implementation of runtime authorized checks, but they'll have to be different because of the different execution flow.