-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Lightweight data fetchers #2953
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
Changes from all commits
b05a2e9
dc93e0d
0917386
755881c
c1f3a0f
56f63ba
0d80881
7331ae4
05061b3
45f3abf
324795d
469723b
14e43af
52c2623
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| import graphql.schema.GraphQLScalarType; | ||
| import graphql.schema.GraphQLSchema; | ||
| import graphql.schema.GraphQLType; | ||
| import graphql.schema.LightDataFetcher; | ||
| import graphql.util.FpKit; | ||
| import graphql.util.LogKit; | ||
| import org.slf4j.Logger; | ||
|
|
@@ -237,68 +238,59 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC | |
| MergedField field = parameters.getField(); | ||
| GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType(); | ||
| GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField()); | ||
|
|
||
| GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); | ||
| GraphQLOutputType fieldType = fieldDef.getType(); | ||
|
|
||
| // if the DF (like PropertyDataFetcher) does not use the arguments of execution step info then dont build any | ||
| Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize( | ||
| () -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType)); | ||
| Supplier<Map<String, Object>> argumentValues = () -> executionStepInfo.get().getArguments(); | ||
| // if the DF (like PropertyDataFetcher) does not use the arguments or execution step info then dont build any | ||
|
|
||
| Supplier<ExecutableNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo); | ||
| Supplier<DataFetchingEnvironment> dataFetchingEnvironment = FpKit.intraThreadMemoize(() -> { | ||
|
|
||
| // DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern | ||
| DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalizedFieldSupplier); | ||
| QueryDirectives queryDirectives = new QueryDirectivesImpl(field, | ||
| executionContext.getGraphQLSchema(), | ||
| executionContext.getCoercedVariables().toMap(), | ||
| executionContext.getGraphQLContext(), | ||
| executionContext.getLocale()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These all move down into the supplier of the DataFetchingEnv - they are ONLY needed for it so UNTIL we ask for the DFE - none of this code is run
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its a bit hard to see - open the fill view to see it |
||
| Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize( | ||
| () -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType)); | ||
|
|
||
| Supplier<Map<String, Object>> argumentValues = () -> executionStepInfo.get().getArguments(); | ||
|
|
||
| Supplier<ExecutableNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo); | ||
|
|
||
| // DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern | ||
| DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldDef.getType(), normalizedFieldSupplier); | ||
| QueryDirectives queryDirectives = new QueryDirectivesImpl(field, | ||
| executionContext.getGraphQLSchema(), | ||
| executionContext.getCoercedVariables().toMap(), | ||
| executionContext.getGraphQLContext(), | ||
| executionContext.getLocale()); | ||
|
|
||
| DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext) | ||
| .source(parameters.getSource()) | ||
| .localContext(parameters.getLocalContext()) | ||
| .arguments(argumentValues) | ||
| .fieldDefinition(fieldDef) | ||
| .mergedField(parameters.getField()) | ||
| .fieldType(fieldType) | ||
| .executionStepInfo(executionStepInfo) | ||
| .parentType(parentType) | ||
| .selectionSet(fieldCollector) | ||
| .queryDirectives(queryDirectives) | ||
| .build(); | ||
|
|
||
| return newDataFetchingEnvironment(executionContext) | ||
| .source(parameters.getSource()) | ||
| .localContext(parameters.getLocalContext()) | ||
| .arguments(argumentValues) | ||
| .fieldDefinition(fieldDef) | ||
| .mergedField(parameters.getField()) | ||
| .fieldType(fieldDef.getType()) | ||
| .executionStepInfo(executionStepInfo) | ||
| .parentType(parentType) | ||
| .selectionSet(fieldCollector) | ||
| .queryDirectives(queryDirectives) | ||
| .build(); | ||
| }); | ||
| DataFetcher<?> dataFetcher = codeRegistry.getDataFetcher(parentType, fieldDef); | ||
|
|
||
| Instrumentation instrumentation = executionContext.getInstrumentation(); | ||
|
|
||
| InstrumentationFieldFetchParameters instrumentationFieldFetchParams = new InstrumentationFieldFetchParameters(executionContext, environment, parameters, dataFetcher instanceof TrivialDataFetcher); | ||
| InstrumentationFieldFetchParameters instrumentationFieldFetchParams = new InstrumentationFieldFetchParameters(executionContext, dataFetchingEnvironment, parameters, dataFetcher instanceof TrivialDataFetcher); | ||
| InstrumentationContext<Object> fetchCtx = nonNullCtx(instrumentation.beginFieldFetch(instrumentationFieldFetchParams, | ||
| executionContext.getInstrumentationState()) | ||
| ); | ||
|
|
||
| CompletableFuture<Object> fetchedValue; | ||
| dataFetcher = instrumentation.instrumentDataFetcher(dataFetcher, instrumentationFieldFetchParams, executionContext.getInstrumentationState()); | ||
| ExecutionId executionId = executionContext.getExecutionId(); | ||
| try { | ||
| Object fetchedValueRaw = dataFetcher.get(environment); | ||
| fetchedValue = Async.toCompletableFuture(fetchedValueRaw); | ||
| } catch (Exception e) { | ||
| if (logNotSafe.isDebugEnabled()) { | ||
| logNotSafe.debug(String.format("'%s', field '%s' fetch threw exception", executionId, executionStepInfo.get().getPath()), e); | ||
| } | ||
| CompletableFuture<Object> fetchedValue = invokeDataFetcher(executionContext, parameters, fieldDef, dataFetchingEnvironment, dataFetcher); | ||
|
|
||
| fetchedValue = new CompletableFuture<>(); | ||
| fetchedValue.completeExceptionally(e); | ||
| } | ||
| fetchCtx.onDispatched(fetchedValue); | ||
| return fetchedValue | ||
| .handle((result, exception) -> { | ||
| fetchCtx.onCompleted(result, exception); | ||
| if (exception != null) { | ||
| return handleFetchingException(executionContext, environment, exception); | ||
| return handleFetchingException(executionContext, dataFetchingEnvironment.get(), exception); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we ONLY use the DFE here if we have an exception which is least trodden path |
||
| } else { | ||
| return CompletableFuture.completedFuture(result); | ||
| } | ||
|
|
@@ -307,6 +299,25 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC | |
| .thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result)); | ||
| } | ||
|
|
||
| private CompletableFuture<Object> invokeDataFetcher(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDef, Supplier<DataFetchingEnvironment> dataFetchingEnvironment, DataFetcher<?> dataFetcher) { | ||
| CompletableFuture<Object> fetchedValue; | ||
| try { | ||
| Object fetchedValueRaw; | ||
| if (dataFetcher instanceof LightDataFetcher) { | ||
| fetchedValueRaw = ((LightDataFetcher<?>) dataFetcher).get(fieldDef, parameters.getSource(), dataFetchingEnvironment); | ||
| } else { | ||
| fetchedValueRaw = dataFetcher.get(dataFetchingEnvironment.get()); | ||
| } | ||
| fetchedValue = Async.toCompletableFuture(fetchedValueRaw); | ||
| } catch (Exception e) { | ||
| if (logNotSafe.isDebugEnabled()) { | ||
| logNotSafe.debug(String.format("'%s', field '%s' fetch threw exception", executionContext.getExecutionId(), parameters.getPath()), e); | ||
| } | ||
| fetchedValue = Async.exceptionallyCompletedFuture(e); | ||
| } | ||
| return fetchedValue; | ||
| } | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved the fetch call into its own method to reduce method complexity |
||
| protected Supplier<ExecutableNormalizedField> getNormalizedField(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Supplier<ExecutionStepInfo> executionStepInfo) { | ||
| Supplier<ExecutableNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree(); | ||
| return () -> normalizedQuery.get().getNormalizedField(parameters.getField(), executionStepInfo.get().getObjectType(), executionStepInfo.get().getPath()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,24 +8,26 @@ | |
| import graphql.execution.instrumentation.InstrumentationState; | ||
| import graphql.schema.DataFetchingEnvironment; | ||
|
|
||
| import java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * Parameters sent to {@link Instrumentation} methods | ||
| */ | ||
| @PublicApi | ||
| public class InstrumentationFieldFetchParameters extends InstrumentationFieldParameters { | ||
| private final DataFetchingEnvironment environment; | ||
| private final Supplier<DataFetchingEnvironment> environment; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs to be a supplier until they ask for it |
||
| private final ExecutionStrategyParameters executionStrategyParameters; | ||
| private final boolean trivialDataFetcher; | ||
|
|
||
| public InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, DataFetchingEnvironment environment, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) { | ||
| super(getExecutionContext, environment::getExecutionStepInfo); | ||
| public InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, Supplier<DataFetchingEnvironment> environment, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) { | ||
| super(getExecutionContext, () -> environment.get().getExecutionStepInfo()); | ||
| this.environment = environment; | ||
| this.executionStrategyParameters = executionStrategyParameters; | ||
| this.trivialDataFetcher = trivialDataFetcher; | ||
| } | ||
|
|
||
| private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, DataFetchingEnvironment environment, InstrumentationState instrumentationState, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) { | ||
| super(getExecutionContext, environment::getExecutionStepInfo, instrumentationState); | ||
| private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, Supplier<DataFetchingEnvironment> environment, InstrumentationState instrumentationState, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) { | ||
| super(getExecutionContext, () -> environment.get().getExecutionStepInfo(), instrumentationState); | ||
| this.environment = environment; | ||
| this.executionStrategyParameters = executionStrategyParameters; | ||
| this.trivialDataFetcher = trivialDataFetcher; | ||
|
|
@@ -45,13 +47,13 @@ private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext | |
| @Override | ||
| public InstrumentationFieldFetchParameters withNewState(InstrumentationState instrumentationState) { | ||
| return new InstrumentationFieldFetchParameters( | ||
| this.getExecutionContext(), this.getEnvironment(), | ||
| this.getExecutionContext(), this.environment, | ||
| instrumentationState, executionStrategyParameters, trivialDataFetcher); | ||
| } | ||
|
|
||
|
|
||
| public DataFetchingEnvironment getEnvironment() { | ||
| return environment; | ||
| return environment.get(); | ||
| } | ||
|
|
||
| public boolean isTrivialDataFetcher() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package graphql.schema; | ||
|
|
||
| import graphql.TrivialDataFetcher; | ||
|
|
||
| import java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * A {@link LightDataFetcher} is a specialised version of {@link DataFetcher} that is passed more lightweight arguments | ||
| * when it is asked to fetch values. The most common example of this is the {@link PropertyDataFetcher} which does not need | ||
| * all the {@link DataFetchingEnvironment} values to perform its duties. | ||
| * | ||
| * @param <T> for two | ||
| */ | ||
| public interface LightDataFetcher<T> extends TrivialDataFetcher<T> { | ||
|
|
||
| /** | ||
| * This is called to by the engine to get a value from the source object in a lightweight fashion. Only the field | ||
| * and source object are passed in a materialised way. The more heavy weight {@link DataFetchingEnvironment} is wrapped | ||
| * in a supplier that is only created on demand. | ||
| * <p> | ||
| * If you are a lightweight data fetcher (like {@link PropertyDataFetcher} is) then you can implement this method to have a more lightweight | ||
| * method invocation. However, if you need field arguments etc. during fetching (most custom fetchers will) then you should use implement | ||
| * {@link #get(DataFetchingEnvironment)}. | ||
| * | ||
| * @param fieldDefinition the graphql field definition | ||
| * @param sourceObject the source object to get a value from | ||
| * @param environmentSupplier a supplier of the {@link DataFetchingEnvironment} that creates it lazily | ||
| * | ||
| * @return a value of type T. May be wrapped in a {@link graphql.execution.DataFetcherResult} | ||
| * | ||
| * @throws Exception to relieve the implementations from having to wrap checked exceptions. Any exception thrown | ||
| * from a {@code DataFetcher} will eventually be handled by the registered {@link graphql.execution.DataFetcherExceptionHandler} | ||
| * and the related field will have a value of {@code null} in the result. | ||
| */ | ||
| T get(GraphQLFieldDefinition fieldDefinition, Object sourceObject, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a new interface to keep Andi happy and to better delineate what the data fetcher does. Is it light or heavy? Its MUST be a DataFetcher however because the CodeRegistry requires a DataFetcher when registered |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DFE creation is now one big supplier