@@ -94,7 +94,10 @@ Here is how you might put this in place:
9494
9595{{< highlight java "linenos=table" >}}
9696
97+ //
9798 // a batch loader function that will be called with N or more keys for batch loading
99+ // This can be a singleton object since its stateless
100+ //
98101 BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
99102 @Override
100103 public CompletionStage<List<Object>> load(List<String> keys) {
@@ -105,8 +108,6 @@ Here is how you might put this in place:
105108 }
106109 };
107110
108- // a data loader for characters that points to the character batch loader
109- DataLoader<String, Object> characterDataLoader = new DataLoader<>(characterBatchLoader);
110111
111112 //
112113 // use this data loader in the data fetchers associated with characters and put them into
@@ -115,7 +116,8 @@ Here is how you might put this in place:
115116 DataFetcher heroDataFetcher = new DataFetcher() {
116117 @Override
117118 public Object get(DataFetchingEnvironment environment) {
118- return characterDataLoader.load("2001"); // R2D2
119+ DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
120+ return dataLoader.load("2001"); // R2D2
119121 }
120122 };
121123
@@ -124,24 +126,22 @@ Here is how you might put this in place:
124126 public Object get(DataFetchingEnvironment environment) {
125127 StarWarsCharacter starWarsCharacter = environment.getSource();
126128 List<String> friendIds = starWarsCharacter.getFriendIds();
127- return characterDataLoader.loadMany(friendIds);
129+ DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
130+ return dataLoader.loadMany(friendIds);
128131 }
129132 };
130133
131- //
132- // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
133- // in this case there is 1 but you can have many
134- //
135- DataLoaderRegistry registry = new DataLoaderRegistry();
136- registry.register("character", characterDataLoader);
137134
138135 //
139- // this instrumentation implementation will dispatch all the dataloaders
136+ // this instrumentation implementation will dispatched all the data loaders
140137 // as each level fo the graphql query is executed and hence make batched objects
141138 // available to the query and the associated DataFetchers
142139 //
140+ DataLoaderDispatcherInstrumentationOptions options = DataLoaderDispatcherInstrumentationOptions
141+ .newOptions().includeStatistics(true);
142+
143143 DataLoaderDispatcherInstrumentation dispatcherInstrumentation
144- = new DataLoaderDispatcherInstrumentation(registry );
144+ = new DataLoaderDispatcherInstrumentation(options );
145145
146146 //
147147 // now build your graphql object and execute queries on it.
@@ -152,11 +152,34 @@ Here is how you might put this in place:
152152 .instrumentation(dispatcherInstrumentation)
153153 .build();
154154
155+ //
156+ // a data loader for characters that points to the character batch loader
157+ //
158+ // Since data loaders are stateful, they are created per execution request.
159+ //
160+ DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(characterBatchLoader);
161+
162+ //
163+ // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
164+ // in this case there is 1 but you can have many.
165+ //
166+ // Also note that the data loaders are created per execution request
167+ //
168+ DataLoaderRegistry registry = new DataLoaderRegistry();
169+ registry.register("character", characterDataLoader);
170+
171+ ExecutionInput executionInput = newExecutionInput()
172+ .query(getQuery())
173+ .dataLoaderRegistry(registry)
174+ .build();
175+
176+ ExecutionResult executionResult = graphQL.execute(executionInput);
177+
178+
155179{{< / highlight >}}
156180
157- One thing to note is the above only works if you use ` DataLoaderDispatcherInstrumentation ` which makes sure ` dataLoader.dispatch() `
158- is called. If this was not in place, then all the promises to data will never be dispatched ot the batch loader function
159- and hence nothing would ever resolve.
181+ In this example we explicitly added the ` DataLoaderDispatcherInstrumentation ` because we wanted to tweak its options. However
182+ it will be automatically added for you if you don't add it manually.
160183
161184## Data Loader only works with AsyncExecutionStrategy
162185
@@ -174,37 +197,54 @@ may get `caching` of values but you will not get `batching` of them.
174197If you are serving web requests then the data can be specific to the user requesting it. If you have user specific data then you will not want to
175198cache data meant for user A to then later give it to user B in a subsequent request.
176199
177- The scope of your DataLoader instances is important. You might want to create them per web request to
178- ensure data is only cached within that web request and no more.
200+ The scope of your DataLoader instances is important. You will want to create them per web request to
201+ ensure data is only cached within that web request and no more. It also ensures that a `` dispatch `` call
202+ only affects that graphql execution and no other.
179203
180- If your data can be shared across web requests then you might want to scope your data loaders so they survive
181- longer than the web request say .
204+ DataLoaders by default act as caches. If they have seen a value before for a key then they will automatically return
205+ it in order to be efficient .
182206
183- But if you are doing per request data loaders then creating a new set of ` GraphQL ` and ` DataLoader ` objects per
184- request is super cheap. It's the ` GraphQLSchema ` creation that can be expensive, especially if you are using graphql SDL parsing .
207+ If your data can be shared across web requests then you might want to change the caching implementation of your data loaders so they share
208+ data via a caching layer say like memcached or redis .
185209
186- Structure your code so that the schema is statically held, perhaps in a static variable or in a singleton IoC component but
187- build out a new ` GraphQL ` set of objects on each request.
210+ You still create data loaders per request, however the caching layer will allow data sharing (if that's suitable).
188211
189212
190213{{< highlight java "linenos=table" >}}
191214
192- GraphQLSchema staticSchema = staticSchema_Or_MayBeFrom_IoC_Injection();
215+ CacheMap<String, Object> crossRequestCacheMap = new CacheMap<String, Object>() {
216+ @Override
217+ public boolean containsKey(String key) {
218+ return redisIntegration.containsKey(key);
219+ }
193220
194- DataLoaderRegistry registry = new DataLoaderRegistry();
195- registry.register("character", getCharacterDataLoader());
221+ @Override
222+ public Object get(String key) {
223+ return redisIntegration.getValue(key);
224+ }
196225
197- DataLoaderDispatcherInstrumentation dispatcherInstrumentation
198- = new DataLoaderDispatcherInstrumentation(registry);
226+ @Override
227+ public CacheMap<String, Object> set(String key, Object value) {
228+ redisIntegration.setValue(key, value);
229+ return this;
230+ }
199231
200- GraphQL graphQL = GraphQL.newGraphQL(staticSchema)
201- .instrumentation(dispatcherInstrumentation)
202- .build();
232+ @Override
233+ public CacheMap<String, Object> delete(String key) {
234+ redisIntegration.clearKey(key);
235+ return this;
236+ }
237+
238+ @Override
239+ public CacheMap<String, Object> clear() {
240+ redisIntegration.clearAll();
241+ return this;
242+ }
243+ };
203244
204- graphQL.execute("{ helloworld }" );
245+ DataLoaderOptions options = DataLoaderOptions.newOptions().setCacheMap(crossRequestCacheMap );
205246
206- // you can now throw away the GraphQL and hence DataLoaderDispatcherInstrumentation
207- // and DataLoaderRegistry objects since they are really cheap to build per request
247+ DataLoader<String, Object> dataLoader = DataLoader.newDataLoader(batchLoader, options);
208248
209249{{< / highlight >}}
210250
@@ -222,14 +262,16 @@ The following will not work (it will never complete).
222262
223263{{< highlight java "linenos=table" >}}
224264
225- BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
265+ BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
226266 @Override
227267 public CompletionStage<List<Object>> load(List<String> keys) {
228268 return CompletableFuture.completedFuture(getTheseCharacters(keys));
229269 }
230270 };
231271
232- DataLoader<String, Object> characterDataLoader = new DataLoader<>(batchLoader);
272+ DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoader);
273+
274+ // .... later in your data fetcher
233275
234276 DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
235277 @Override
@@ -239,10 +281,12 @@ The following will not work (it will never complete).
239281 //
240282 return CompletableFuture.supplyAsync(() -> {
241283 String argId = environment.getArgument("id");
242- return characterDataLoader.load(argId);
284+ DataLoader<String, Object> characterLoader = environment.getDataLoader("characterLoader");
285+ return characterLoader.load(argId);
243286 });
244287 }
245288 };
289+
246290{{< / highlight >}}
247291
248292In the example above, the call to ` characterDataLoader.load(argId) ` can happen some time in the future on another thread. The graphql-java
@@ -263,7 +307,9 @@ The following is how you can still have asynchronous code, by placing it into th
263307 }
264308 };
265309
266- DataLoader<String, Object> characterDataLoader = new DataLoader<>(batchLoader);
310+ DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoader);
311+
312+ // .... later in your data fetcher
267313
268314 DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
269315 @Override
@@ -272,14 +318,77 @@ The following is how you can still have asynchronous code, by placing it into th
272318 // This is OK
273319 //
274320 String argId = environment.getArgument("id");
275- return characterDataLoader.load(argId);
321+ DataLoader<String, Object> characterLoader = environment.getDataLoader("characterLoader");
322+ return characterLoader.load(argId);
276323 }
277324 };
325+
278326{{< / highlight >}}
279327
280328Notice above the ` characterDataLoader.load(argId) ` returns immediately. This will enqueue the call for data until a later time when all
281329the graphql fields are dispatched.
282330
283331Then later when the ` DataLoader ` is dispatched, it's ` BatchLoader ` function is called. This code can be asynchronous so that if you have multiple batch loader
284332functions they all can run at once. In the code above ` CompletableFuture.supplyAsync(() -> getTheseCharacters(keys)); ` will run the `` getTheseCharacters ``
285- method in another thread.
333+ method in another thread.
334+
335+ # Passing context to your data loader
336+
337+ The data loader library supports two types of context being passed to the batch loader. The first is
338+ an overall context object per dataloader and the second is a map of per loaded key context objects.
339+
340+ This allows you to pass in the extra details you may need to make downstream calls. The dataloader key is used
341+ in the caching of results but the context objects can be made available to help with the call.
342+
343+ So in the example below we have an overall security context object that gives out a call token and we also pass the graphql source
344+ object to each `` dataLoader.load() `` call.
345+
346+ {{< highlight java "linenos=table" >}}
347+
348+ BatchLoaderWithContext<String, Object> batchLoaderWithCtx = new BatchLoaderWithContext<String, Object>() {
349+
350+ @Override
351+ public CompletionStage<List<Object>> load(List<String> keys, BatchLoaderEnvironment loaderContext) {
352+ //
353+ // we can have an overall context object
354+ SecurityContext securityCtx = loaderContext.getContext();
355+ //
356+ // and we can have a per key set of context objects
357+ Map<Object, Object> keysToSourceObjects = loaderContext.getKeyContexts();
358+
359+ return CompletableFuture.supplyAsync(() -> getTheseCharacters(securityCtx.getToken(), keys, keysToSourceObjects));
360+ }
361+ };
362+
363+ // ....
364+
365+ SecurityContext securityCtx = SecurityContext.newSecurityContext();
366+
367+ BatchLoaderContextProvider contextProvider = new BatchLoaderContextProvider() {
368+ @Override
369+ public Object getContext() {
370+ return securityCtx;
371+ }
372+ };
373+ //
374+ // this creates an overall context for the dataloader
375+ //
376+ DataLoaderOptions loaderOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider);
377+ DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoaderWithCtx, loaderOptions);
378+
379+ // .... later in your data fetcher
380+
381+ DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
382+ @Override
383+ public Object get(DataFetchingEnvironment environment) {
384+ String argId = environment.getArgument("id");
385+ Object source = environment.getSource();
386+ //
387+ // you can pass per load call contexts
388+ //
389+ return characterDataLoader.load(argId, source);
390+ }
391+ };
392+
393+ {{< / highlight >}}
394+
0 commit comments