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
Originally I was going to post this in the graphql-java maintainers Slack room but I thought... open source right. So I will publish it as an idea. This is not guaranteed to happen but I thought be open on this anyway.
Today its difficult to write new execution engines and it's even hard for the graphql-team to make a code base that allows you to.
We originally envisaged graphql.execution.ExecutionStrategy would be the base for new engines however it used inheritance as a model and that just doesn't work. We should have used composition instead but we are where we are now.
A new engine ends up using a certain calling pattern and ExecutionStrategy assumes you use the basic calling pattern it follows. find all fields / for each field / fetch / descend / repeat
The same is true of graphql.execution.instrumentation.Instrumentation. Its methods related to execution really reflect the code paths taken by ExecutionStrategy - eg first its beginField and then beginFieldFetch and then beginFieldComplete but in theory a new engine could combine fields in some way or decide to execute in a completely different manner.
So the execution methods in Instrumentation are really tied to the engine underneath.
So while we need a way to indicate what "engine" you are using in graphql.GraphQL it should not have to be the built in strategies like it is today
ExecutionStrategy is currently @PublicSpi and I wish it was not because creating your own is fraught and not that useful given it locks you into lots of code decisions it has encoded. We dont want to make breaking changes if we can but given that few people are expected to derive from ExecutionStrategy we may have more lee way then other areas.
I propose the following - naming is hard so these are provisional
This would be the entry point to the engine. (I named it ExecutionEngineStrategy in sympathy for the current naming)
graphql.GraphQL.Builder would be changed so it has these as input
public Builder queryExecutionStrategy(ExecutionEngineStrategy executionStrategy)
graphql.execution.ExecutionStrategy would be changed to be one
public abstract class ExecutionStrategy implements ExecutionEngineStrategy {
So this should be API compatible with today for the most part.
Right now graphql.execution.Execution#executeOperation has a lot of code in it t set up the call to the engine. However its really again about how the current graphql.execution.ExecutionStrategy works. For example it uses FieldCollector and some new engine might want to be based on graphql.normalized.ExecutableNormalizedOperation say and that code is not needed for it.
So this code needs to be moved into the graphql.execution.ExecutionStrategy of today rather than stay out of it. It may involve repeated code in other engines however I think its ok to repeat code for while new engine.
This leads us to Instrumentation - as I said earlier, the call pattern of the graphql.execution.ExecutionStrategy is baked into graphql.execution.instrumentation.Instrumentation
I propose we invent a new interface ExecutionEngineInstrumentation that the current graphql.execution.instrumentation.Instrumentation derives from.
The non "execution methods" would be retained and moved up into ExecutionEngineInstrumentation - eg
createState()
beginExecution()
bad naming - but its really a begin request
beginParse()
beginValidation()
executeOperation()
This is the very start before we kick in the engine
There is some complications inside the builder of GraphQL
private static Instrumentation checkInstrumentationDefaultState(Instrumentation instrumentation, boolean doNotAddDefaultInstrumentations) {
if (doNotAddDefaultInstrumentations) {
return instrumentation == null ? SimplePerformantInstrumentation.INSTANCE : instrumentation;
}
if (instrumentation instanceof DataLoaderDispatcherInstrumentation) {
return instrumentation;
}
if (instrumentation instanceof NoContextChainedInstrumentation) {
return instrumentation;
}
if (instrumentation == null) {
return new DataLoaderDispatcherInstrumentation();
}
//
// if we don't have a DataLoaderDispatcherInstrumentation in play, we add one. We want DataLoader to be 1st class in graphql without requiring
// people to remember to wire it in. Later we may decide to have more default instrumentations but for now it's just the one
//
List<Instrumentation> instrumentationList = new ArrayList<>();
if (instrumentation instanceof ChainedInstrumentation) {
instrumentationList.addAll(((ChainedInstrumentation) instrumentation).getInstrumentations());
} else {
instrumentationList.add(instrumentation);
}
boolean containsDLInstrumentation = instrumentationList.stream().anyMatch(instr -> instr instanceof DataLoaderDispatcherInstrumentation);
if (!containsDLInstrumentation) {
instrumentationList.add(new DataLoaderDispatcherInstrumentation());
}
return new ChainedInstrumentation(instrumentationList);
}
It starts to default the Instrumentation in use with an assumption it needs to be one of the following.
I am not sure what to do here off hand - whether to leave it (and assume its the old way) or push this further down the line some how into the engine?? This needs further design thought.
Now in theory a new engine MAY not want the single field at a time graphql.schema.DataFetcher - it could fetch batches of fields at once or use some other mechanism. However PropertyDataFetcher is likely to be very useful to the new engine (how to read POJO properties fast) and also graphql.schema.GraphQLCodeRegistry stores DataFetcher -> FieldCordinate and this seems needed.
So I think DataFetcher should be retained as is and if you have new entry points for fetching data with new shapes you MAY choose to implement DataFetcher BUT have new entry methods for fetching that are engine specific. You have a dud SPI method - graphql.schema.DataFetcher#get(DataFetchingEnvironment environment) but so be it - the new engine would be in charge of which method it calls a priori
We do this today with graphql.schema.LightDataFetcher and its special T get(GraphQLFieldDefinition fieldDefinition, Object sourceObject, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception; method. It's an engine specific method that's known a priori to be called by the engine if a fetcher is of the right shape.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Originally I was going to post this in the graphql-java maintainers Slack room but I thought... open source right. So I will publish it as an idea. This is not guaranteed to happen but I thought be open on this anyway.
Today its difficult to write new execution engines and it's even hard for the graphql-team to make a code base that allows you to.
We originally envisaged
graphql.execution.ExecutionStrategy
would be the base for new engines however it used inheritance as a model and that just doesn't work. We should have used composition instead but we are where we are now.A new engine ends up using a certain calling pattern and
ExecutionStrategy
assumes you use the basic calling pattern it follows.find all fields / for each field / fetch / descend / repeat
The same is true of
graphql.execution.instrumentation.Instrumentation
. Its methods related to execution really reflect the code paths taken byExecutionStrategy
- eg first itsbeginField
and thenbeginFieldFetch
and thenbeginFieldComplete
but in theory a new engine could combine fields in some way or decide to execute in a completely different manner.So the execution methods in
Instrumentation
are really tied to the engine underneath.So while we need a way to indicate what "engine" you are using in
graphql.GraphQL
it should not have to be the built in strategies like it is todayExecutionStrategy
is currently@PublicSpi
and I wish it was not because creating your own is fraught and not that useful given it locks you into lots of code decisions it has encoded. We dont want to make breaking changes if we can but given that few people are expected to derive fromExecutionStrategy
we may have more lee way then other areas.I propose the following - naming is hard so these are provisional
This would be the entry point to the engine. (I named it
ExecutionEngineStrategy
in sympathy for the current naming)graphql.GraphQL.Builder
would be changed so it has these as inputgraphql.execution.ExecutionStrategy
would be changed to be oneSo this should be API compatible with today for the most part.
Right now
graphql.execution.Execution#executeOperation
has a lot of code in it t set up the call to the engine. However its really again about how the currentgraphql.execution.ExecutionStrategy
works. For example it uses FieldCollector and some new engine might want to be based ongraphql.normalized.ExecutableNormalizedOperation
say and that code is not needed for it.So this code needs to be moved into the
graphql.execution.ExecutionStrategy
of today rather than stay out of it. It may involve repeated code in other engines however I think its ok to repeat code for while new engine.This leads us to
Instrumentation
- as I said earlier, the call pattern of thegraphql.execution.ExecutionStrategy
is baked intographql.execution.instrumentation.Instrumentation
I propose we invent a new interface
ExecutionEngineInstrumentation
that the currentgraphql.execution.instrumentation.Instrumentation
derives from.The non "execution methods" would be retained and moved up into
ExecutionEngineInstrumentation
- egThere is some complications inside the builder of
GraphQL
It starts to default the
Instrumentation
in use with an assumption it needs to be one of the following.I am not sure what to do here off hand - whether to leave it (and assume its the old way) or push this further down the line some how into the engine?? This needs further design thought.
Now in theory a new engine MAY not want the single field at a time
graphql.schema.DataFetcher
- it could fetch batches of fields at once or use some other mechanism. HoweverPropertyDataFetcher
is likely to be very useful to the new engine (how to read POJO properties fast) and alsographql.schema.GraphQLCodeRegistry
storesDataFetcher -> FieldCordinate
and this seems needed.So I think
DataFetcher
should be retained as is and if you have new entry points for fetching data with new shapes you MAY choose to implementDataFetcher
BUT have new entry methods for fetching that are engine specific. You have a dud SPI method -graphql.schema.DataFetcher#get(DataFetchingEnvironment environment)
but so be it - the new engine would be in charge of which method it calls a prioriWe do this today with
graphql.schema.LightDataFetcher
and its specialT get(GraphQLFieldDefinition fieldDefinition, Object sourceObject, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception;
method. It's an engine specific method that's known a priori to be called by the engine if a fetcher is of the right shape.Beta Was this translation helpful? Give feedback.
All reactions