Skip to content

Commit aa1ee77

Browse files
committed
Document how to configure custom ExecutionStrategy
Closes gh-832
1 parent 7dd9cf5 commit aa1ee77

File tree

3 files changed

+70
-28
lines changed

3 files changed

+70
-28
lines changed

spring-graphql-docs/modules/ROOT/pages/request-execution.adoc

+33
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,39 @@ https://github.com/graphql-java/graphql-java-extended-validation[Extended Valida
196196
library.
197197

198198

199+
[[execution.graphqlsource.execution-strategy]]
200+
=== `ExecutionStrategy`
201+
202+
An `ExecutionStrategy` in GraphQL Java drives the fetching of requested fields.
203+
To create an `ExecutionStrategy`, you need to provide a `DataFetcherExceptionHandler`.
204+
By default, Spring for GraphQL creates the exception handler to use as described in
205+
xref:request-execution.adoc#execution.exceptions[Exceptions] and sets it on the
206+
`GraphQL.Builder`. GraphQL Java then uses that to create `AsyncExecutionStrategy`
207+
instances with the configured exception handler.
208+
209+
If you need to create a custom `ExecutionStrategy`, you can detect
210+
``DataFetcherExceptionResolver``s and create an exception handler in the same way, and use
211+
it to create the custom `ExecutionStrategy`. For example, in a Spring Boot application:
212+
213+
[source,java,indent=0,subs="verbatim,quotes"]
214+
----
215+
@Bean
216+
GraphQlSourceBuilderCustomizer sourceBuilderCustomizer(
217+
ObjectProvider<DataFetcherExceptionResolver> resolvers) {
218+
219+
DataFetcherExceptionHandler exceptionHandler =
220+
DataFetcherExceptionResolver.createExceptionHandler(resolvers.stream().toList());
221+
222+
AsyncExecutionStrategy strategy = new CustomAsyncExecutionStrategy(exceptionHandler);
223+
224+
return sourceBuilder -> sourceBuilder.configureGraphQl(builder ->
225+
builder.queryExecutionStrategy(strategy).mutationExecutionStrategy(strategy));
226+
}
227+
----
228+
229+
230+
231+
199232
[[execution.graphqlsource.schema-transformation]]
200233
=== Schema Transformation
201234

spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolver.java

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,10 +26,12 @@
2626

2727
/**
2828
* Contract to resolve exceptions from {@link graphql.schema.DataFetcher}s.
29-
* Implementations are typically declared as beans in Spring configuration and
30-
* are invoked sequentially until one emits a List of {@link GraphQLError}s.
29+
* Resolves are typically declared as Spring beans and invoked in turn until one
30+
* resolves the exception by emitting a (possibly empty) {@code GraphQLError} list.
31+
* Use the static factory method {@link #createExceptionHandler} to create a
32+
* {@link DataFetcherExceptionHandler} from a list of resolvers.
3133
*
32-
* <p>Most resolver implementations can extend
34+
* <p>Resolver implementations can extend
3335
* {@link DataFetcherExceptionResolverAdapter} and override one of its
3436
* {@link DataFetcherExceptionResolverAdapter#resolveToSingleError resolveToSingleError} or
3537
* {@link DataFetcherExceptionResolverAdapter#resolveToMultipleErrors resolveToMultipleErrors}
@@ -85,13 +87,13 @@ protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironmen
8587
}
8688

8789
/**
88-
* Factory method to create a {@link DataFetcherExceptionResolver} from a
89-
* list of resolvers. Spring for GraphQL uses this method to set
90-
* {@link graphql.GraphQL.Builder#defaultDataFetcherExceptionHandler(DataFetcherExceptionHandler)}
91-
* from resolvers found in Spring configuration, and that default handler
92-
* is used in turn to create each {@code ExecutionStrategy}. Applications
93-
* may also find this factory method useful when creating a custom
94-
* {@code ExecutionStrategy}.
90+
* Factory method to create a {@link DataFetcherExceptionHandler} from a
91+
* list of {@link DataFetcherExceptionResolver}'s. This is used internally
92+
* in {@link AbstractGraphQlSourceBuilder} to set the exception handler on
93+
* {@link graphql.GraphQL.Builder}, which in turn is used to create
94+
* {@link graphql.execution.ExecutionStrategy}'s. Applications may also use
95+
* this method to create an exception handler when they to need to initialize
96+
* a custom {@code ExecutionStrategy}.
9597
* <p>Resolvers are invoked in turn until one resolves the exception by
9698
* emitting a (possibly empty) {@code GraphQLError} list. If the exception
9799
* remains unresolved, the handler creates a {@code GraphQLError} with

spring-graphql/src/main/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandler.java

+24-17
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@
4040
* {@link DataFetcherExceptionHandler} that invokes {@link DataFetcherExceptionResolver}'s
4141
* in a sequence until one returns a list of {@link GraphQLError}'s.
4242
*
43+
* <p>Use {@link DataFetcherExceptionResolver#createExceptionHandler(List)} to
44+
* create an instance.
45+
*
4346
* @author Rossen Stoyanchev
4447
*/
4548
class ExceptionResolversExceptionHandler implements DataFetcherExceptionHandler {
4649

4750
private static final Log logger = LogFactory.getLog(ExceptionResolversExceptionHandler.class);
4851

52+
4953
private final List<DataFetcherExceptionResolver> resolvers;
5054

55+
5156
/**
5257
* Create an instance.
5358
* @param resolvers the resolvers to use
@@ -60,9 +65,11 @@ class ExceptionResolversExceptionHandler implements DataFetcherExceptionHandler
6065

6166
@Override
6267
@SuppressWarnings("deprecation")
63-
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters params) {
64-
Throwable exception = unwrapException(params);
65-
DataFetchingEnvironment env = params.getDataFetchingEnvironment();
68+
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(
69+
DataFetcherExceptionHandlerParameters handlerParameters) {
70+
71+
Throwable exception = unwrapException(handlerParameters);
72+
DataFetchingEnvironment env = handlerParameters.getDataFetchingEnvironment();
6673
ContextSnapshot snapshot = ContextSnapshot.captureFrom(env.getGraphQlContext());
6774
try {
6875
return Flux.fromIterable(this.resolvers)
@@ -80,34 +87,34 @@ public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(Data
8087
}
8188
}
8289

83-
private DataFetcherExceptionHandlerResult handleResolverError(
84-
Throwable resolverException, Throwable originalException, DataFetchingEnvironment environment) {
85-
86-
if (logger.isWarnEnabled()) {
87-
logger.warn("Failure while resolving " + originalException.getMessage(), resolverException);
88-
}
89-
return createInternalError(originalException, environment);
90-
}
91-
9290
private Throwable unwrapException(DataFetcherExceptionHandlerParameters params) {
9391
Throwable ex = params.getException();
9492
return ((ex instanceof CompletionException) ? ex.getCause() : ex);
9593
}
9694

9795
private void logResolvedException(Throwable ex, DataFetcherExceptionHandlerResult result) {
9896
if (logger.isDebugEnabled()) {
99-
logger.debug("Resolved " + ex.getClass().getSimpleName() +
100-
" to GraphQL error(s): " + result.getErrors(), ex);
97+
String name = ex.getClass().getSimpleName();
98+
logger.debug("Resolved " + name + " to GraphQL error(s): " + result.getErrors(), ex);
99+
}
100+
}
101+
102+
private DataFetcherExceptionHandlerResult handleResolverError(
103+
Throwable resolverException, Throwable originalException, DataFetchingEnvironment env) {
104+
105+
if (logger.isWarnEnabled()) {
106+
logger.warn("Failure while resolving " + originalException.getMessage(), resolverException);
101107
}
108+
return createInternalError(originalException, env);
102109
}
103110

104-
private DataFetcherExceptionHandlerResult createInternalError(Throwable ex, DataFetchingEnvironment environment) {
105-
ExecutionId executionId = environment.getExecutionId();
111+
private DataFetcherExceptionHandlerResult createInternalError(Throwable ex, DataFetchingEnvironment env) {
112+
ExecutionId executionId = env.getExecutionId();
106113
if (logger.isErrorEnabled()) {
107114
logger.error("Unresolved " + ex.getClass().getSimpleName() + " for executionId " + executionId, ex);
108115
}
109116
return DataFetcherExceptionHandlerResult
110-
.newResult(GraphqlErrorBuilder.newError(environment)
117+
.newResult(GraphqlErrorBuilder.newError(env)
111118
.errorType(ErrorType.INTERNAL_ERROR)
112119
.message(ErrorType.INTERNAL_ERROR + " for " + executionId)
113120
.build())

0 commit comments

Comments
 (0)