From dfec37025fa90ae247714930c15ab75b626e4eec Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 8 Aug 2021 19:58:51 +1000 Subject: [PATCH 1/3] Upgrading back to dataloader 3.x --- build.gradle | 2 +- .../DataLoaderCacheCanBeAsyncTest.groovy | 133 ++++++++++++++++++ .../readme/DataLoaderBatchingExamples.java | 48 +++---- 3 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy diff --git a/build.gradle b/build.gradle index 4de0e18684..6a7eaeefc0 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { compileOnly 'org.jetbrains:annotations:20.1.0' implementation 'org.antlr:antlr4-runtime:' + antlrVersion implementation 'org.slf4j:slf4j-api:' + slf4jVersion - api 'com.graphql-java:java-dataloader:2.2.3' + api 'com.graphql-java:java-dataloader:2021-08-08T17-39-22-d8078e0' api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion antlr 'org.antlr:antlr4:' + antlrVersion implementation 'com.google.guava:guava:30.0-jre' diff --git a/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy b/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy new file mode 100644 index 0000000000..3b57bf9780 --- /dev/null +++ b/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy @@ -0,0 +1,133 @@ +package graphql.execution.instrumentation + +import graphql.ExecutionInput +import graphql.GraphQL +import graphql.TestUtil +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import org.dataloader.BatchLoader +import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory +import org.dataloader.DataLoaderOptions +import org.dataloader.DataLoaderRegistry +import org.dataloader.ValueCache +import spock.lang.Specification + +import java.util.concurrent.CompletableFuture + +class DataLoaderCacheCanBeAsyncTest extends Specification { + + def sdl = """ + type Query { + user(id : ID) : User + } + + type User { + id : ID + name : String + } + """ + + static class CustomValueCache implements ValueCache { + Map store = [:] + + int getRandomNumber(int min, int max) { + Random random = new Random() + return random.nextInt(max - min) + min + } + + @Override + CompletableFuture get(String key) { + return CompletableFuture.supplyAsync({ + Thread.sleep(getRandomNumber(100, 500)) + if (store.containsKey(key)) { + return store.get(key) + } + throw new RuntimeException("Key Missing") + }) + } + + @Override + CompletableFuture set(String key, Object value) { + return CompletableFuture.supplyAsync({ + Thread.sleep(getRandomNumber(100, 500)) + store.put(key, value) + return value + }) + } + + @Override + CompletableFuture delete(String key) { + return CompletableFuture.completedFuture(null) + } + + @Override + CompletableFuture clear() { + return CompletableFuture.completedFuture(null) + } + } + + DataLoaderRegistry registry + GraphQL graphQL + + void setup() { + + BatchLoader userBatchLoader = { List keys -> + return CompletableFuture.supplyAsync({ -> + Thread.sleep(100) + def users = [] + for (String k : keys) { + users.add([id: k, name: k + "Name"]) + } + users + }) + } + + + def valueCache = new CustomValueCache() + valueCache.store.put("a", [id: "cachedA", name: "cachedAName"]) + + DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(valueCache).setCachingEnabled(true) + DataLoader userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader, options) + + registry = DataLoaderRegistry.newRegistry() + .register("users", userDataLoader) + .build() + + DataFetcher userDF = { DataFetchingEnvironment env -> + def id = env.getArgument("id") + def loader = env.getDataLoader("users") + return loader.load(id) + } + + def schema = TestUtil.schema(sdl, [Query: [user: userDF]]) + graphQL = GraphQL.newGraphQL(schema).build() + + } + + def "can execute data loader calls"() { + def query = ''' + query { + a: user(id : "a") { + id name + } + b: user(id : "b") { + id name + } + c: user(id : "c") { + id name + } + } + ''' + def executionInput = ExecutionInput.newExecutionInput(query).dataLoaderRegistry(registry).build() + + when: + def er = graphQL.execute(executionInput) + then: + er.errors.isEmpty() + er.data == [a: [id: "cachedA", name: "cachedAName"], + b: [id: "b", name: "bName"], + c: [id: "c", name: "cName"], + ] + } +} diff --git a/src/test/groovy/readme/DataLoaderBatchingExamples.java b/src/test/groovy/readme/DataLoaderBatchingExamples.java index df8fddb0ca..8d13a4581f 100644 --- a/src/test/groovy/readme/DataLoaderBatchingExamples.java +++ b/src/test/groovy/readme/DataLoaderBatchingExamples.java @@ -12,10 +12,11 @@ import org.dataloader.BatchLoaderContextProvider; import org.dataloader.BatchLoaderEnvironment; import org.dataloader.BatchLoaderWithContext; -import org.dataloader.CacheMap; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; import org.dataloader.DataLoaderOptions; import org.dataloader.DataLoaderRegistry; +import org.dataloader.ValueCache; import java.util.List; import java.util.Map; @@ -105,7 +106,7 @@ public Object get(DataFetchingEnvironment environment) { // // Since data loaders are stateful, they are created per execution request. // - DataLoader characterDataLoader = DataLoader.newDataLoader(characterBatchLoader); + DataLoader characterDataLoader = DataLoaderFactory.newDataLoader(characterBatchLoader); // // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together @@ -130,18 +131,19 @@ public boolean containsKey(String key) { return false; } - public Object getValue(String key) { + public CompletableFuture getValue(String key) { return null; } - public CacheMap setValue(String key, Object value) { + public CompletableFuture setValue(String key, Object value) { return null; } - public void clearKey(String key) { + public CompletableFuture clearKey(String key) { + return null; } - public CacheMap clearAll() { + public CompletableFuture clearAll() { return null; } } @@ -157,39 +159,31 @@ public CompletionStage> load(List keys) { private void changeCachingSolutionOfDataLoader() { - CacheMap crossRequestCacheMap = new CacheMap() { - @Override - public boolean containsKey(String key) { - return redisIntegration.containsKey(key); - } - + ValueCache crossRequestValueCache = new ValueCache() { @Override - public Object get(String key) { + public CompletableFuture get(String key) { return redisIntegration.getValue(key); } @Override - public CacheMap set(String key, Object value) { - redisIntegration.setValue(key, value); - return this; + public CompletableFuture set(String key, Object value) { + return redisIntegration.setValue(key, value); } @Override - public CacheMap delete(String key) { - redisIntegration.clearKey(key); - return this; + public CompletableFuture delete(String key) { + return redisIntegration.clearKey(key); } @Override - public CacheMap clear() { - redisIntegration.clearAll(); - return this; + public CompletableFuture clear() { + return redisIntegration.clearAll(); } }; - DataLoaderOptions options = DataLoaderOptions.newOptions().setCacheMap(crossRequestCacheMap); + DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(crossRequestValueCache); - DataLoader dataLoader = DataLoader.newDataLoader(batchLoader, options); + DataLoader dataLoader = DataLoaderFactory.newDataLoader(batchLoader, options); } private void doNotUseAsyncInYouDataFetcher() { @@ -201,7 +195,7 @@ public CompletionStage> load(List keys) { } }; - DataLoader characterDataLoader = DataLoader.newDataLoader(batchLoader); + DataLoader characterDataLoader = DataLoaderFactory.newDataLoader(batchLoader); // .... later in your data fetcher @@ -229,7 +223,7 @@ public CompletionStage> load(List keys) { } }; - DataLoader characterDataLoader = DataLoader.newDataLoader(batchLoader); + DataLoader characterDataLoader = DataLoaderFactory.newDataLoader(batchLoader); // .... later in your data fetcher @@ -277,7 +271,7 @@ public Object getContext() { // this creates an overall context for the dataloader // DataLoaderOptions loaderOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider); - DataLoader characterDataLoader = DataLoader.newDataLoader(batchLoaderWithCtx, loaderOptions); + DataLoader characterDataLoader = DataLoaderFactory.newDataLoader(batchLoaderWithCtx, loaderOptions); // .... later in your data fetcher From 5f04c641483839c37d52f08f29aede380df581fb Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 9 Aug 2021 20:30:44 +1000 Subject: [PATCH 2/3] Upgrading back to dataloader 3.1.0-beta1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6a7eaeefc0..aa2961f57d 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { compileOnly 'org.jetbrains:annotations:20.1.0' implementation 'org.antlr:antlr4-runtime:' + antlrVersion implementation 'org.slf4j:slf4j-api:' + slf4jVersion - api 'com.graphql-java:java-dataloader:2021-08-08T17-39-22-d8078e0' + api 'com.graphql-java:java-dataloader:3.1.0-beta1' api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion antlr 'org.antlr:antlr4:' + antlrVersion implementation 'com.google.guava:guava:30.0-jre' From 1e5788668c0f4d1a599c70418b736fa011eaa55d Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 9 Aug 2021 21:28:00 +1000 Subject: [PATCH 3/3] Upgrading back to dataloader 3.1.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aa2961f57d..f0993168a6 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { compileOnly 'org.jetbrains:annotations:20.1.0' implementation 'org.antlr:antlr4-runtime:' + antlrVersion implementation 'org.slf4j:slf4j-api:' + slf4jVersion - api 'com.graphql-java:java-dataloader:3.1.0-beta1' + api 'com.graphql-java:java-dataloader:3.1.0' api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion antlr 'org.antlr:antlr4:' + antlrVersion implementation 'com.google.guava:guava:30.0-jre'