Skip to content

Commit c59c31a

Browse files
RusticFlarebbakerman
authored andcommitted
Add a helper class for making asynchronous data fetchers (#798)
* Ignore files generated by Eclipse * Add a helper class for making asynchronous data fetchers * Add the ability to set the executor an async data fetcher will run in * Reinstate original documentation and add further section for new method * Make the fields of AsynchronousDataFetcher final
1 parent 0255331 commit c59c31a

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ classes
88
_site
99
generated-src/
1010
out
11-
docs/_build/
11+
docs/_build/
12+
/bin/
13+
\.classpath
14+
\.project
15+
\.settings/

docs/execution.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,14 @@ The code above is written in long form. With Java 8 lambdas it can be written m
345345
The graphql-java engine ensures that all the ``CompletableFuture`` objects are composed together to provide an execution result
346346
that follows the graphql specification.
347347

348+
There is a helpful shortcut in graphql-java to create asynchronous data fetchers.
349+
Use ``graphql.schema.AsynchronousDataFetcher.async(DataFetcher<T>)`` to wrap a
350+
``DataFetcher``. This can be used with static imports to produce more readable code.
351+
352+
.. code-block:: java
353+
354+
DataFetcher userDataFetcher = async(environment -> fetchUserViaHttp(environment.getArgument("userId")));
355+
348356
Execution Strategies
349357
--------------------
350358

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package graphql.schema;
2+
3+
import static graphql.Assert.assertNotNull;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.concurrent.Executor;
7+
import java.util.concurrent.ForkJoinPool;
8+
9+
import graphql.PublicApi;
10+
11+
/**
12+
* A modifier type that indicates the underlying data fetcher is run asynchronously
13+
*/
14+
@PublicApi
15+
public class AsynchronousDataFetcher<T> implements DataFetcher<CompletableFuture<T>> {
16+
17+
/**
18+
* A factory method for creating asynchronous data fetchers so that when used with
19+
* static imports allows more readable code such as:
20+
* <p>
21+
* {@code .dataFetcher(async(fooDataFetcher))}
22+
* <p>
23+
* By default this will run in the {@link ForkJoinPool#commonPool()}. You can set
24+
* your own {@link Executor} with {@link #asyncWithExecutor(DataFetcher, Executor)}
25+
*
26+
* @param wrappedDataFetcher the data fetcher to run asynchronously
27+
*
28+
* @return a {@link DataFetcher} that will run the wrappedDataFetcher asynchronously
29+
*/
30+
public static <T> AsynchronousDataFetcher<T> async(DataFetcher<T> wrappedDataFetcher) {
31+
return new AsynchronousDataFetcher<>(wrappedDataFetcher);
32+
}
33+
34+
/**
35+
* A factory method for creating asynchronous data fetchers and setting the
36+
* {@link Executor} they run in so that when used with static imports allows
37+
* more readable code such as:
38+
* <p>
39+
* {@code .dataFetcher(asyncWithExecutor(fooDataFetcher, fooPool))}
40+
*
41+
* @param wrappedDataFetcher the data fetcher to run asynchronously
42+
* @param executor to run the asynchronous data fetcher in
43+
*
44+
* @return a {@link DataFetcher} that will run the wrappedDataFetcher asynchronously in
45+
* the given {@link Executor}
46+
*/
47+
public static <T> AsynchronousDataFetcher<T> asyncWithExecutor(DataFetcher<T> wrappedDataFetcher,
48+
Executor executor) {
49+
return new AsynchronousDataFetcher<>(wrappedDataFetcher, executor);
50+
}
51+
52+
private final DataFetcher<T> wrappedDataFetcher;
53+
private final Executor executor;
54+
55+
public AsynchronousDataFetcher(DataFetcher<T> wrappedDataFetcher) {
56+
this(wrappedDataFetcher, ForkJoinPool.commonPool());
57+
}
58+
59+
public AsynchronousDataFetcher(DataFetcher<T> wrappedDataFetcher, Executor executor) {
60+
this.wrappedDataFetcher = assertNotNull(wrappedDataFetcher, "wrappedDataFetcher can't be null");
61+
this.executor = assertNotNull(executor, "executor can't be null");
62+
}
63+
64+
@Override
65+
public CompletableFuture<T> get(DataFetchingEnvironment environment) {
66+
return CompletableFuture.supplyAsync(() -> wrappedDataFetcher.get(environment), executor);
67+
}
68+
69+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package graphql.schema
2+
3+
import graphql.GraphQL
4+
import graphql.StarWarsData
5+
import graphql.TestUtil
6+
import graphql.execution.FieldCollector
7+
import graphql.language.AstPrinter
8+
import graphql.language.Field
9+
import graphql.schema.idl.MapEnumValuesProvider
10+
import graphql.schema.idl.RuntimeWiring
11+
import spock.lang.Specification
12+
13+
import java.util.concurrent.CompletableFuture
14+
import java.util.stream.Collectors
15+
16+
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring
17+
18+
19+
class AsynchronousDataFetcherTest extends Specification {
20+
21+
def "A data fetcher can be made asynchronous with AsynchronousDataFetcher#async"() {
22+
given:
23+
DataFetchingEnvironment environment = Mock(DataFetchingEnvironment)
24+
25+
when:
26+
DataFetcher asyncDataFetcher = AsynchronousDataFetcher.async({ env -> "value" })
27+
28+
then:
29+
asyncDataFetcher.get(environment) instanceof CompletableFuture
30+
asyncDataFetcher.get(environment).get() == "value"
31+
}
32+
}

0 commit comments

Comments
 (0)