Skip to content

Commit

Permalink
Add a helper class for making asynchronous data fetchers (#798)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
RusticFlare authored and bbakerman committed Oct 27, 2017
1 parent 0255331 commit c59c31a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ classes
_site _site
generated-src/ generated-src/
out out
docs/_build/ docs/_build/
/bin/
\.classpath
\.project
\.settings/
8 changes: 8 additions & 0 deletions docs/execution.rst
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ The code above is written in long form. With Java 8 lambdas it can be written m
The graphql-java engine ensures that all the ``CompletableFuture`` objects are composed together to provide an execution result The graphql-java engine ensures that all the ``CompletableFuture`` objects are composed together to provide an execution result
that follows the graphql specification. that follows the graphql specification.


There is a helpful shortcut in graphql-java to create asynchronous data fetchers.
Use ``graphql.schema.AsynchronousDataFetcher.async(DataFetcher<T>)`` to wrap a
``DataFetcher``. This can be used with static imports to produce more readable code.

.. code-block:: java
DataFetcher userDataFetcher = async(environment -> fetchUserViaHttp(environment.getArgument("userId")));
Execution Strategies Execution Strategies
-------------------- --------------------


Expand Down
69 changes: 69 additions & 0 deletions src/main/java/graphql/schema/AsynchronousDataFetcher.java
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,69 @@
package graphql.schema;

import static graphql.Assert.assertNotNull;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;

import graphql.PublicApi;

/**
* A modifier type that indicates the underlying data fetcher is run asynchronously
*/
@PublicApi
public class AsynchronousDataFetcher<T> implements DataFetcher<CompletableFuture<T>> {

/**
* A factory method for creating asynchronous data fetchers so that when used with
* static imports allows more readable code such as:
* <p>
* {@code .dataFetcher(async(fooDataFetcher))}
* <p>
* By default this will run in the {@link ForkJoinPool#commonPool()}. You can set
* your own {@link Executor} with {@link #asyncWithExecutor(DataFetcher, Executor)}
*
* @param wrappedDataFetcher the data fetcher to run asynchronously
*
* @return a {@link DataFetcher} that will run the wrappedDataFetcher asynchronously
*/
public static <T> AsynchronousDataFetcher<T> async(DataFetcher<T> wrappedDataFetcher) {
return new AsynchronousDataFetcher<>(wrappedDataFetcher);
}

/**
* A factory method for creating asynchronous data fetchers and setting the
* {@link Executor} they run in so that when used with static imports allows
* more readable code such as:
* <p>
* {@code .dataFetcher(asyncWithExecutor(fooDataFetcher, fooPool))}
*
* @param wrappedDataFetcher the data fetcher to run asynchronously
* @param executor to run the asynchronous data fetcher in
*
* @return a {@link DataFetcher} that will run the wrappedDataFetcher asynchronously in
* the given {@link Executor}
*/
public static <T> AsynchronousDataFetcher<T> asyncWithExecutor(DataFetcher<T> wrappedDataFetcher,
Executor executor) {
return new AsynchronousDataFetcher<>(wrappedDataFetcher, executor);
}

private final DataFetcher<T> wrappedDataFetcher;
private final Executor executor;

public AsynchronousDataFetcher(DataFetcher<T> wrappedDataFetcher) {
this(wrappedDataFetcher, ForkJoinPool.commonPool());
}

public AsynchronousDataFetcher(DataFetcher<T> wrappedDataFetcher, Executor executor) {
this.wrappedDataFetcher = assertNotNull(wrappedDataFetcher, "wrappedDataFetcher can't be null");
this.executor = assertNotNull(executor, "executor can't be null");
}

@Override
public CompletableFuture<T> get(DataFetchingEnvironment environment) {
return CompletableFuture.supplyAsync(() -> wrappedDataFetcher.get(environment), executor);
}

}
32 changes: 32 additions & 0 deletions src/test/groovy/graphql/schema/AsynchronousDataFetcherTest.groovy
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,32 @@
package graphql.schema

import graphql.GraphQL
import graphql.StarWarsData
import graphql.TestUtil
import graphql.execution.FieldCollector
import graphql.language.AstPrinter
import graphql.language.Field
import graphql.schema.idl.MapEnumValuesProvider
import graphql.schema.idl.RuntimeWiring
import spock.lang.Specification

import java.util.concurrent.CompletableFuture
import java.util.stream.Collectors

import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring


class AsynchronousDataFetcherTest extends Specification {

def "A data fetcher can be made asynchronous with AsynchronousDataFetcher#async"() {
given:
DataFetchingEnvironment environment = Mock(DataFetchingEnvironment)

when:
DataFetcher asyncDataFetcher = AsynchronousDataFetcher.async({ env -> "value" })

then:
asyncDataFetcher.get(environment) instanceof CompletableFuture
asyncDataFetcher.get(environment).get() == "value"
}
}

0 comments on commit c59c31a

Please sign in to comment.