All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.execution.instrumentation.dataloader;

import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.PublicApi;
import graphql.execution.AsyncExecutionStrategy;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStrategy;
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.SimpleInstrumentationContext;
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.language.OperationDefinition;
import graphql.schema.DataFetcher;
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import org.dataloader.stats.Statistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * This graphql {@link graphql.execution.instrumentation.Instrumentation} will dispatch
 * all the contained {@link org.dataloader.DataLoader}s when each level of the graphql
 * query is executed.
 * 

* This allows you to use {@link org.dataloader.DataLoader}s in your {@link graphql.schema.DataFetcher}s * to optimal loading of data. *

* A DataLoaderDispatcherInstrumentation will be automatically added to the {@link graphql.GraphQL} * instrumentation list if one is not present. * * @see org.dataloader.DataLoader * @see org.dataloader.DataLoaderRegistry */ @PublicApi public class DataLoaderDispatcherInstrumentation extends SimpleInstrumentation { private static final Logger log = LoggerFactory.getLogger(DataLoaderDispatcherInstrumentation.class); private final DataLoaderDispatcherInstrumentationOptions options; /** * Creates a DataLoaderDispatcherInstrumentation with the default options */ public DataLoaderDispatcherInstrumentation() { this(DataLoaderDispatcherInstrumentationOptions.newOptions()); } /** * Creates a DataLoaderDispatcherInstrumentation with the specified options * * @param options the options to control the behaviour */ public DataLoaderDispatcherInstrumentation(DataLoaderDispatcherInstrumentationOptions options) { this.options = options; } @Override public InstrumentationState createState(InstrumentationCreateStateParameters parameters) { return new DataLoaderDispatcherInstrumentationState(log, parameters.getExecutionInput().getDataLoaderRegistry()); } @Override public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); if (state.isAggressivelyBatching()) { return dataFetcher; } // // currently only AsyncExecutionStrategy with DataLoader and hence this allows us to "dispatch" // on every object if its not using aggressive batching for other execution strategies // which allows them to work if used. return (DataFetcher) environment -> { Object obj = dataFetcher.get(environment); immediatelyDispatch(state); return obj; }; } private void immediatelyDispatch(DataLoaderDispatcherInstrumentationState state) { state.getApproach().dispatch(); } @Override public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); // // during #instrumentExecutionInput they could have enhanced the data loader registry // so we grab it now just before the query operation gets started // DataLoaderRegistry finalRegistry = parameters.getExecutionContext().getDataLoaderRegistry(); state.setDataLoaderRegistry(finalRegistry); if (!isDataLoaderCompatibleExecution(parameters.getExecutionContext())) { state.setAggressivelyBatching(false); } return new SimpleInstrumentationContext<>(); } private boolean isDataLoaderCompatibleExecution(ExecutionContext executionContext) { // // currently we only support Query operations and ONLY with AsyncExecutionStrategy as the query ES // This may change in the future but this is the fix for now // if (executionContext.getOperationDefinition().getOperation() == OperationDefinition.Operation.QUERY) { ExecutionStrategy queryStrategy = executionContext.getQueryStrategy(); if (queryStrategy instanceof AsyncExecutionStrategy) { return true; } } return false; } @Override public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); // // if there are no data loaders, there is nothing to do // if (state.hasNoDataLoaders()) { return new ExecutionStrategyInstrumentationContext() { @Override public void onDispatched(CompletableFuture result) { } @Override public void onCompleted(ExecutionResult result, Throwable t) { } }; } return state.getApproach().beginExecutionStrategy(parameters.withNewState(state.getState())); } @Override public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); // // if there are no data loaders, there is nothing to do // if (state.hasNoDataLoaders()) { return new SimpleInstrumentationContext<>(); } return state.getApproach().beginFieldFetch(parameters.withNewState(state.getState())); } @Override public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { if (!options.isIncludeStatistics()) { return CompletableFuture.completedFuture(executionResult); } DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); Map currentExt = executionResult.getExtensions(); Map statsMap = new LinkedHashMap<>(currentExt == null ? Collections.emptyMap() : currentExt); Map dataLoaderStats = buildStatsMap(state); statsMap.put("dataloader", dataLoaderStats); if (log.isDebugEnabled()) { log.debug("Data loader stats : {}", dataLoaderStats); } return CompletableFuture.completedFuture(new ExecutionResultImpl(executionResult.getData(), executionResult.getErrors(), statsMap)); } private Map buildStatsMap(DataLoaderDispatcherInstrumentationState state) { DataLoaderRegistry dataLoaderRegistry = state.getDataLoaderRegistry(); Statistics allStats = dataLoaderRegistry.getStatistics(); Map statsMap = new LinkedHashMap<>(); statsMap.put("overall-statistics", allStats.toMap()); Map individualStatsMap = new LinkedHashMap<>(); for (String dlKey : dataLoaderRegistry.getKeys()) { DataLoader dl = dataLoaderRegistry.getDataLoader(dlKey); Statistics statistics = dl.getStatistics(); individualStatsMap.put(dlKey, statistics.toMap()); } statsMap.put("individual-statistics", individualStatsMap); return statsMap; } }