You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There have been a lot of solutions to the n+1 problem in GraphQL. Patterns like the DataLoader gather up the requests and allow the developer to make a single request for the group. These come with their own complications, and learning curve.
If we include the main idea of these solutions at the GraphQL level, we can get similar capabilities all with reduced complexity. To accomplish this we need to decouple the list type from the non-list type, which allows developers to make a single external request for a list of results.
List-field resolvers would work just a little differently from the typical resolver. When a request is being parsed, if the field type is a list type, then we should check the resolvers for a list of objects of that type. If such a resolver is found then we first resolve list, and call the list field resolver rather than mapping over the individual resolvers for every item in that field. The resolver would then call the resolver with a parent object that is a list, and one which must resolve to an array of the same length, where each element corresponds to the field for value for the parent of the same index. Returning an array of a different length would result in an error being thrown, as it would be impossible to field values back onto the parent object.
This is a lot more difficult to describe than to show an example. So let's consider this schema:
I'm going to use prisma for the example, and we can assume that the structure in the database matches the GraphQL objects. Given that, here
constresolvers={Query: {thing: (__,{ id })=>prisma.thing.findOne({where: { id }})things: ()=>prisma.thing.findMany()},Thing: {stuff: ({ id })=>prisma.stuff.findMany({where: {thingId: id}}),// This is an approximation of the default field resolver,// used for comparison to the array-field defined belowproperty: (thing)=>thing.property},// When a field is of type `[Thing]`, these are the resolvers for the subfields. "[Thing]": {stuff: async(things)=>{constparentIds=things.map((n)=>n.id)constallStuff=awaitprisma.stuff.findMany({where: {thingId: {in: parentsIds}}})// Notice how we are not just returning an array, but instead an array of arrays,// where for _each_ parent element, we return the value of the corresponding field.// The easiest way to accomplish this is to simply map over the parent list (`things`)returnthings.map((thing)=>allStuff.filter(({ thingId })=>thingId===thing.id))// This would be an error as `allStuff` is of type `Stuff[]`, but the resolver expects a return// value of type `Stuff[][]`returnallStuff},// This is an approximation of what would become the default resolver for `[Things].property`property: async(things,args,context,info)=>things.map(// Don't actually do this, but this illustrates how list-fields are resolved by default.(thing)=>resolvers.Thing.property(thing,args,context,info))},}
And finally the following query:
QueryThingsWithStuff {
things {
idpropertystuff {
iddescription
}
}
}
As far as I am aware all GraphQL servers in the Javascript ecosystem, to resolve this query would call resolvers.Query.things, and then for each result would call, resolvers.Thing.stuff for as many things that are returned from the first query, ie the infamous n+1 problem.
My hypothetical GraphQL server would again start by calling resolvers.Query.things, but instead this time it calls resolvers["[Thing]"].stuff, resulting in just two database queries.
If a more complete example with data is necessary I can write that up later.
The text was updated successfully, but these errors were encountered:
One complication is that a field can return a list of interfaces, where items may include different types of objects at runtime that fulfill the abstract type, with possibly different selection sets.
if you have a proposal for modifying the execution flow, of course, you can always submit a PR for a review. This repository tries to match the specification as much as possible, but an optimizations that do not obfuscate too much can always be considered.
It might make sense for the PR within this repository to simply allow for a more flexible architecture where the actual change of execution flow is a separate user space package.
We’re cleaning house, so I am going to close this issue, but feel free to reopen if you think there is more to discuss. I think the trickiness is within the implementation, so you might want to sketch out a pull request if you want to pursue this further. I would suggest a bare bones implementation to just see if the solution can handle interfaces, and we can continue the discussion from there.
I would also suggest considering a different approach such as grafast which is more comprehensive, but certainly can handle what you are describing.
There have been a lot of solutions to the n+1 problem in GraphQL. Patterns like the
DataLoader
gather up the requests and allow the developer to make a single request for the group. These come with their own complications, and learning curve.If we include the main idea of these solutions at the GraphQL level, we can get similar capabilities all with reduced complexity. To accomplish this we need to decouple the list type from the non-list type, which allows developers to make a single external request for a list of results.
List-field resolvers would work just a little differently from the typical resolver. When a request is being parsed, if the field type is a list type, then we should check the resolvers for a list of objects of that type. If such a resolver is found then we first resolve list, and call the list field resolver rather than mapping over the individual resolvers for every item in that field. The resolver would then call the resolver with a parent object that is a list, and one which must resolve to an array of the same length, where each element corresponds to the field for value for the parent of the same index. Returning an array of a different length would result in an error being thrown, as it would be impossible to field values back onto the parent object.
This is a lot more difficult to describe than to show an example. So let's consider this schema:
I'm going to use prisma for the example, and we can assume that the structure in the database matches the GraphQL objects. Given that, here
And finally the following query:
As far as I am aware all GraphQL servers in the Javascript ecosystem, to resolve this query would call
resolvers.Query.things
, and then for each result would call,resolvers.Thing.stuff
for as many things that are returned from the first query, ie the infamous n+1 problem.My hypothetical GraphQL server would again start by calling
resolvers.Query.things
, but instead this time it callsresolvers["[Thing]"].stuff
, resulting in just two database queries.If a more complete example with data is necessary I can write that up later.
The text was updated successfully, but these errors were encountered: