Skip to content

Commit b2b066c

Browse files
authored
Updating doco for new data loader version (#53)
1 parent ea5d82a commit b2b066c

File tree

1 file changed

+41
-49
lines changed

1 file changed

+41
-49
lines changed

content/documentation/master/batching.md

Lines changed: 41 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ description: How to avoid the dreaded N+1 calls for data and make your graphql s
88
---
99
# Using Dataloader
1010

11-
If you are using `graphql`, you are likely to making queries on a graph of data (surprise surprise). But it's easy
11+
If you are using `graphql`, you are likely to making queries on a graph of data (no surprises there). However, it's easy
1212
to implement inefficient code with naive loading of a graph of data.
1313

1414
Using `java-dataloader` will help you to make this a more efficient process by both caching and batching requests for that graph of data items. If `dataloader`
1515
has seen a data item before, it will have cached the value and will return it without having to ask for it again.
1616

17-
Imagine we have the StarWars query outlined below. It asks us to find a hero and their friend's names and their friend's friend's
17+
Imagine we have the StarWars query outlined below. It asks us to find a hero, and their friend's names, and their friend's friend's
1818
names. It is likely that many of these people will be friends in common.
1919

2020
{{< highlight graphql "linenos=table" >}}
@@ -32,7 +32,7 @@ names. It is likely that many of these people will be friends in common.
3232
}
3333
{{< / highlight >}}
3434

35-
The result of this query is displayed below. You can see that Han, Leia, Luke and R2-D2 are a tight knit bunch of friends and
35+
The result of this query is displayed below. You can see that Han, Leia, Luke and R2-D2 are a tight-knit bunch of friends and
3636
share many friends in common.
3737

3838
{{< highlight json "linenos=table" >}}
@@ -74,10 +74,10 @@ share many friends in common.
7474

7575
A naive implementation would call a `DataFetcher` to retrieve a person object every time it was invoked.
7676

77-
In this case it would be *15* calls over the network. Even though the group of people have a lot of common friends.
77+
In this case it would be *15* calls over the network, even though the group of people have a lot of common friends.
7878
With `dataloader` you can make the `graphql` query much more efficient.
7979

80-
As `graphql` descends each level of the query (e.g. as it processes `hero` and then `friends` and then for each their `friends`),
80+
As `graphql` descends each level of the query (e.g., as it processes `hero` and then `friends` and then for each of their `friends`),
8181
the data loader is called to "promise" to deliver a person object. At each level `dataloader.dispatch()` will be
8282
called to fire off the batch requests for that part of the query. With caching turned on (the default) then
8383
any previously returned person will be returned as-is for no cost.
@@ -86,7 +86,7 @@ In the above example there are only *5* unique people mentioned but with caching
8686
*3* calls to the batch loader function. *3* calls over the network or to a database is much better than *15* calls, you will agree.
8787

8888
If you use capabilities like `java.util.concurrent.CompletableFuture.supplyAsync()` then you can make it even more efficient by making the
89-
the remote calls asynchronous to the rest of the query. This will make it even more timely since multiple calls can happen at once
89+
remote calls asynchronous to the rest of the query. This will make it even more timely since multiple calls can happen at once
9090
if need be.
9191

9292
Here is how you might put this in place:
@@ -102,7 +102,7 @@ Here is how you might put this in place:
102102
@Override
103103
public CompletionStage<List<Object>> load(List<String> keys) {
104104
//
105-
// we use supplyAsync() of values here for maximum parallelisation
105+
// we use supplyAsync() of values here for maximum parellisation
106106
//
107107
return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
108108
}
@@ -113,15 +113,15 @@ Here is how you might put this in place:
113113
// use this data loader in the data fetchers associated with characters and put them into
114114
// the graphql schema (not shown)
115115
//
116-
DataFetcher heroDataFetcher = new DataFetcher() {
116+
DataFetcher<?> heroDataFetcher = new DataFetcher<Object>() {
117117
@Override
118118
public Object get(DataFetchingEnvironment environment) {
119119
DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
120120
return dataLoader.load("2001"); // R2D2
121121
}
122122
};
123123

124-
DataFetcher friendsDataFetcher = new DataFetcher() {
124+
DataFetcher<?> friendsDataFetcher = new DataFetcher<Object>() {
125125
@Override
126126
public Object get(DataFetchingEnvironment environment) {
127127
StarWarsCharacter starWarsCharacter = environment.getSource();
@@ -159,7 +159,7 @@ Here is how you might put this in place:
159159
//
160160
// Since data loaders are stateful, they are created per execution request.
161161
//
162-
DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(characterBatchLoader);
162+
DataLoader<String, Object> characterDataLoader = DataLoaderFactory.newDataLoader(characterBatchLoader);
163163

164164
//
165165
// DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
@@ -180,7 +180,7 @@ Here is how you might put this in place:
180180

181181
{{< / highlight >}}
182182

183-
In this example we explicitly added the `DataLoaderDispatcherInstrumentation` because we wanted to tweak its options. However
183+
In this example we explicitly added the `DataLoaderDispatcherInstrumentation` because we wanted to tweak its options. However,
184184
it will be automatically added for you if you don't add it manually.
185185

186186
## Data Loader only works with AsyncExecutionStrategy
@@ -189,9 +189,9 @@ The only execution that works with DataLoader is `graphql.execution.AsyncExecuti
189189
then the most optimal time to dispatch() your load calls is. It does this by deeply tracking how many fields are outstanding and whether they
190190
are list values and so on.
191191

192-
Other execution strategies such as `ExecutorServiceExecutionStrategy` cant do this and hence if the data loader code detects
193-
you are not using `AsyncExecutionStrategy` then it will simple dispatch the data loader as each field is encountered. You
194-
may get `caching` of values but you will not get `batching` of them.
192+
Other execution strategies such as `ExecutorServiceExecutionStrategy` can't do this and hence if the data loader code detects
193+
you are not using `AsyncExecutionStrategy` then it will simply dispatch the data loader as each field is encountered. You
194+
may get `caching` of values, but you will not get `batching` of them.
195195

196196

197197
## Per Request Data Loaders
@@ -204,78 +204,70 @@ ensure data is only cached within that web request and no more. It also ensures
204204
only affects that graphql execution and no other.
205205

206206
DataLoaders by default act as caches. If they have seen a value before for a key then they will automatically return
207-
it in order to be efficient.
207+
it in order to be efficient. They cache promises to a value and optionally the value itself.
208208

209-
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
210-
data via a caching layer say like memcached or redis.
209+
If your data can be shared across web requests then you might want to change the `ValueCache` implementation of your data loaders, so they share
210+
data via caching systems like memcached or redis.
211211

212212
You still create data loaders per request, however the caching layer will allow data sharing (if that's suitable).
213213

214214

215215
{{< highlight java "linenos=table" >}}
216216

217-
CacheMap<String, Object> crossRequestCacheMap = new CacheMap<String, Object>() {
217+
ValueCache<String, Object> crossRequestValueCache = new ValueCache<String, Object>() {
218218
@Override
219-
public boolean containsKey(String key) {
220-
return redisIntegration.containsKey(key);
221-
}
222-
223-
@Override
224-
public Object get(String key) {
219+
public CompletableFuture<Object> get(String key) {
225220
return redisIntegration.getValue(key);
226221
}
227222

228223
@Override
229-
public CacheMap<String, Object> set(String key, Object value) {
230-
redisIntegration.setValue(key, value);
231-
return this;
224+
public CompletableFuture<Object> set(String key, Object value) {
225+
return redisIntegration.setValue(key, value);
232226
}
233227

234228
@Override
235-
public CacheMap<String, Object> delete(String key) {
236-
redisIntegration.clearKey(key);
237-
return this;
229+
public CompletableFuture<Void> delete(String key) {
230+
return redisIntegration.clearKey(key);
238231
}
239232

240233
@Override
241-
public CacheMap<String, Object> clear() {
242-
redisIntegration.clearAll();
243-
return this;
234+
public CompletableFuture<Void> clear() {
235+
return redisIntegration.clearAll();
244236
}
245237
};
246238

247-
DataLoaderOptions options = DataLoaderOptions.newOptions().setCacheMap(crossRequestCacheMap);
239+
DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(crossRequestValueCache);
248240

249-
DataLoader<String, Object> dataLoader = DataLoader.newDataLoader(batchLoader, options);
241+
DataLoader<String, Object> dataLoader = DataLoaderFactory.newDataLoader(batchLoader, options);
250242

251243
{{< / highlight >}}
252244

253245
## Async Calls On Your Batch Loader Function Only
254246

255247
The data loader code pattern works by combining all the outstanding data loader calls into more efficient batch loading calls.
256248

257-
graphql-java tracks what outstanding data loader calls have been made and it is its responsibility to call `dispatch`
249+
graphql-java tracks what outstanding data loader calls have been made, and it is its responsibility to call `dispatch`
258250
in the background at the most optimal time, which is when all graphql fields have been examined and dispatched.
259251

260-
However there is a code pattern that will cause your data loader calls to never complete and these *MUST* be avoided. This bad
261-
pattern consists of making a an asynchronous off thread call to a `DataLoader` in your data fetcher.
252+
However, there is a code pattern that will cause your data loader calls to never complete, and these *MUST* be avoided. This bad
253+
pattern consists of making an asynchronous off thread call to a `DataLoader` in your data fetcher.
262254

263255
The following will not work (it will never complete).
264256

265257
{{< highlight java "linenos=table" >}}
266258

267-
BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
259+
BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
268260
@Override
269261
public CompletionStage<List<Object>> load(List<String> keys) {
270262
return CompletableFuture.completedFuture(getTheseCharacters(keys));
271263
}
272264
};
273265

274-
DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoader);
266+
DataLoader<String, Object> characterDataLoader = DataLoaderFactory.newDataLoader(batchLoader);
275267

276268
// .... later in your data fetcher
277269

278-
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
270+
DataFetcher<?> dataFetcherThatCallsTheDataLoader = new DataFetcher<Object>() {
279271
@Override
280272
public Object get(DataFetchingEnvironment environment) {
281273
//
@@ -295,7 +287,7 @@ In the example above, the call to `characterDataLoader.load(argId)` can happen s
295287
engine has no way of knowing when it's good time to dispatch outstanding `DataLoader` calls and hence the data loader call might never complete
296288
as expected and no results will be returned.
297289

298-
Remember a data loader call is just a promise to actually get a value later when its an optimal time for all outstanding calls to be batched
290+
Remember a data loader call is just a promise to actually get a value later when it's an optimal time for all outstanding calls to be batched
299291
together. The most optimal time is when the graphql field tree has been examined and all field values are currently dispatched.
300292

301293
The following is how you can still have asynchronous code, by placing it into the `BatchLoader` itself.
@@ -309,11 +301,11 @@ The following is how you can still have asynchronous code, by placing it into th
309301
}
310302
};
311303

312-
DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoader);
304+
DataLoader<String, Object> characterDataLoader = DataLoaderFactory.newDataLoader(batchLoader);
313305

314306
// .... later in your data fetcher
315307

316-
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
308+
DataFetcher<?> dataFetcherThatCallsTheDataLoader = new DataFetcher<Object>() {
317309
@Override
318310
public Object get(DataFetchingEnvironment environment) {
319311
//
@@ -337,12 +329,12 @@ method in another thread.
337329
# Passing context to your data loader
338330

339331
The data loader library supports two types of context being passed to the batch loader. The first is
340-
an overall context object per dataloader and the second is a map of per loaded key context objects.
332+
an overall context object per dataloader, and the second is a map of per loaded key context objects.
341333

342334
This allows you to pass in the extra details you may need to make downstream calls. The dataloader key is used
343-
in the caching of results but the context objects can be made available to help with the call.
335+
in the caching of results, but the context objects can be made available to help with the call.
344336

345-
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
337+
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
346338
object to each ``dataLoader.load()`` call.
347339

348340
{{< highlight java "linenos=table" >}}
@@ -376,11 +368,11 @@ object to each ``dataLoader.load()`` call.
376368
// this creates an overall context for the dataloader
377369
//
378370
DataLoaderOptions loaderOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider);
379-
DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(batchLoaderWithCtx, loaderOptions);
371+
DataLoader<String, Object> characterDataLoader = DataLoaderFactory.newDataLoader(batchLoaderWithCtx, loaderOptions);
380372

381373
// .... later in your data fetcher
382374

383-
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
375+
DataFetcher<?> dataFetcherThatCallsTheDataLoader = new DataFetcher<Object>() {
384376
@Override
385377
public Object get(DataFetchingEnvironment environment) {
386378
String argId = environment.getArgument("id");

0 commit comments

Comments
 (0)