@@ -165,13 +165,136 @@ a list of user ids in one call.
165
165
166
166
```
167
167
168
- That said, with key caching turn on (the default), it may still be more efficient using ` dataloader ` than without it.
168
+ That said, with key caching turn on (the default), it will still be more efficient using ` dataloader ` than without it.
169
+
170
+ # Using dataloader in graphql for maximum efficiency
171
+
172
+
173
+ If you are using ` graphql ` , you are likely to making queries on a graph of data (surprise surprise). ` dataloader ` will help
174
+ you to make this a more efficient process by both caching and batching requests for that graph of data items. If ` dataloader `
175
+ has previously see a data item before, it will cached the value and will return it without having to ask for it again.
176
+
177
+ 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
178
+ names. It is likely that many of these people will be friends in common.
179
+
180
+
181
+
182
+ {
183
+ hero {
184
+ name
185
+ friends {
186
+ name
187
+ friends {
188
+ name
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ The result of this query is displayed below. You can see that Han, Leia, Luke and R2-D2 are tight knit bunch of friends and
195
+ share many friends in common.
196
+
197
+
198
+ [hero: [name: 'R2-D2', friends: [
199
+ [name: 'Luke Skywalker', friends: [
200
+ [name: 'Han Solo'], [name: 'Leia Organa'], [name: 'C-3PO'], [name: 'R2-D2']]],
201
+ [name: 'Han Solo', friends: [
202
+ [name: 'Luke Skywalker'], [name: 'Leia Organa'], [name: 'R2-D2']]],
203
+ [name: 'Leia Organa', friends: [
204
+ [name: 'Luke Skywalker'], [name: 'Han Solo'], [name: 'C-3PO'], [name: 'R2-D2']]]]]
205
+ ]
206
+
207
+ A naive implementation would called a ` DataFetcher ` to retrieved a person object every time it was invoked.
208
+
209
+ In this case it would be * 15* calls over the network. Even though the group of people have a lot of common friends.
210
+ With ` dataloader ` you can make the ` graphql ` query much more efficient.
211
+
212
+ As ` graphql ` descends each level of the query ( eg as it processes ` hero ` and then ` friends ` and then for each their ` friends ` ),
213
+ the data loader is called to "promise" to deliver a person object. At each level ` dataloader.dispatch() ` will be
214
+ called to fire off the batch requests for that part of the query. With caching turned on (the default) then
215
+ any previously returned person will be returned as is for no cost.
216
+
217
+ In the above example there are only * 5* unique people mentioned but with caching and batching retrieval in place their will be only
218
+ * 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.
219
+
220
+ If you use capabilities like ` java.util.concurrent.CompletableFuture.supplyAsync() ` then you can make it even more efficient by making the
221
+ the remote calls asynchronous to the rest of the query. This will make it even more timely since multiple calls can happen at once
222
+ if need be.
223
+
224
+ Here is how you might put this in place:
225
+
226
+
227
+ ``` java
228
+
229
+ // a batch loader function that will be called with N or more keys for batch loading
230
+ BatchLoader<String , Object > characterBatchLoader = new BatchLoader<String , Object > () {
231
+ @Override
232
+ public CompletionStage<List<Object > > load (List<String > keys ) {
233
+ //
234
+ // we use supplyAsync() of values here for maximum parellisation
235
+ //
236
+ return CompletableFuture . supplyAsync(() - > getCharacterDataViaBatchHTTPApi(keys));
237
+ }
238
+ };
239
+
240
+ // a data loader for characters that points to the character batch loader
241
+ DataLoader characterDataLoader = new DataLoader<String , Object > (characterBatchLoader);
242
+
243
+ //
244
+ // use this data loader in the data fetchers associated with characters and put them into
245
+ // the graphql schema (not shown)
246
+ //
247
+ DataFetcher heroDataFetcher = new DataFetcher () {
248
+ @Override
249
+ public Object get (DataFetchingEnvironment environment ) {
250
+ return characterDataLoader. load(" 2001" ); // R2D2
251
+ }
252
+ };
253
+
254
+ DataFetcher friendsDataFetcher = new DataFetcher () {
255
+ @Override
256
+ public Object get (DataFetchingEnvironment environment ) {
257
+ StarWarsCharacter starWarsCharacter = environment. getSource();
258
+ List<String > friendIds = starWarsCharacter. getFriendIds();
259
+ return characterDataLoader. loadMany(friendIds);
260
+ }
261
+ };
262
+
263
+ //
264
+ // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
265
+ // in this case there is 1 but you can have many
266
+ //
267
+ DataLoaderRegistry registry = new DataLoaderRegistry ();
268
+ registry. register(characterDataLoader);
269
+
270
+ //
271
+ // this instrumentation implementation will dispatched all the dataloaders
272
+ // as each level fo the graphql query is executed and hence make batched objects
273
+ // available to the query and the associated DataFetchers
274
+ //
275
+ DataLoaderDispatcherInstrumentation dispatcherInstrumentation
276
+ = new DataLoaderDispatcherInstrumentation (registry);
277
+
278
+ //
279
+ // now build your graphql object and execute queries on it.
280
+ // the data loader will be invoked via the data fetchers on the
281
+ // schema fields
282
+ //
283
+ GraphQL graphQL = GraphQL . newGraphQL(buildSchema())
284
+ .instrumentation(dispatcherInstrumentation)
285
+ .build();
286
+ ```
287
+
288
+ One thing to note is the above only works if you use ` DataLoaderDispatcherInstrumentation ` which makes sure ` dataLoader.dispatch() ` is called. If
289
+ this was not in place, then all the promises to data will never be dispatched ot the batch loader function and hence nothing would ever resolve.
290
+
291
+ See below for more details on ` dataLoader.dispatch() `
169
292
170
293
## Differences to reference implementation
171
294
172
295
### Manual dispatching
173
296
174
- The original data loader was written in Javascript for NodeJS. NodeJS is single-threaded in nature, but simulates
297
+ The original [ Facebook DataLoader ] ( https://github.com/facebook/dataloader ) was written in Javascript for NodeJS. NodeJS is single-threaded in nature, but simulates
175
298
asynchronous logic by invoking functions on separate threads in an event loop, as explained
176
299
[ in this post] ( http://stackoverflow.com/a/19823583/3455094 ) on StackOverflow.
177
300
0 commit comments