From b1f1c383eba1a9e1e907cc5ef128227631f57ca7 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:48:36 +1000 Subject: [PATCH 1/5] Add documentation for Chained DataLoaders --- documentation/batching.md | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/documentation/batching.md b/documentation/batching.md index e420108..1c543c0 100644 --- a/documentation/batching.md +++ b/documentation/batching.md @@ -370,3 +370,65 @@ DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() { } }; ``` + +## Chained DataLoaders + +The automatic dispatching of Chained DataLoaders is a new feature included in GraphQL Java 25.0 onwards. Before this version, DataLoaders in chains needed to be manually dispatched. + +A Chained DataLoader is where one DataLoader depends on another, within the same DataFetcher. + +For example in code: +```java +DataFetcher> df1 = env -> { + return env.getDataLoader("name").load("Key1").thenCompose(result -> { + return env.getDataLoader("email").load(result); + }); +}; +``` + +### How do I enable Chained DataLoaders? +As this changes execution order, you must opt-in to Chained DataLoaders by setting key-value pairs in `GraphQLContext`. + +1. Set `DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING` to `true` to enable Chained DataLoaders +2. Provide a `ScheduledExecutorService` to GraphQL Java, with key `DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY` and value implementing `DelayedDataLoaderDispatcherExecutorFactory` + +### What changed in GraphQL Java 25.0? +The DataFetcher in the example above, before version 25.0 would have caused execution to hang, because the first DataLoader ("name") was never dispatched. + +Prior to version 25.0, users of GraphQL Java needed to manually dispatch DataLoaders to ensure execution completed. From version 25.0, the GraphQL Java engine will automatically dispatch Chained DataLoaders. + +If you're looking for more examples, and the technical details, please see [our tests](https://github.com/graphql-java/graphql-java/blob/master/src/test/groovy/graphql/ChainedDataLoaderTest.groovy). + +Note: The GraphQL Java engine can only optimally calculate DataLoader dispatches on a per-level basis. It does not calculate optimal DataLoader dispatching across different levels of an operation's field tree. + +### A special case: Delayed DataLoaders + +In a previous code snippet we demonstrated one DataLoader depending on another DataLoader. + +Another special case is a "delayed" DataLoader, where a DataLoader depends on a slow async task instead. For example, here are two DataFetchers from [a test example](https://github.com/graphql-java/graphql-java/blob/master/src/test/groovy/graphql/ChainedDataLoaderTest.groovy): + +```groovy +def fooDF = { env -> + return supplyAsync { + Thread.sleep(1000) + return "fooFirstValue" + }.thenCompose { + return env.getDataLoader("dl").load(it) + } +} as DataFetcher + +def barDF = { env -> + return supplyAsync { + Thread.sleep(1000) + return "barFirstValue" + }.thenCompose { + return env.getDataLoader("dl").load(it) + } +} as DataFetcher +``` + +By opting in to Chained DataLoaders, GraphQL Java will also calculate when to dispatch "delayed" DataLoaders. + +The default value for the time to wait for these "delayed" DataLoaders is 500,000ns (`DEFAULT_BATCH_WINDOW_NANO_SECONDS_DEFAULT`). If you like, you can configure your own batch window by setting a key-value pair in `GraphQLContext`. Set `DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS` to be your preferred batch window size in nanoseconds. + +Note that the case, where one DataLoader depends on another DataLoader all within the same DataFetcher, is unaffected by this batch window configuration. This window configuration only changes how long to wait for the "delayed" DataLoader case, where a DataLoader depends on another async task. From 9ee08e766a6d5143c09af853359760a7e5244da8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:06:01 +1000 Subject: [PATCH 2/5] Put through comments --- documentation/batching.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/documentation/batching.md b/documentation/batching.md index 1c543c0..6fb3eb5 100644 --- a/documentation/batching.md +++ b/documentation/batching.md @@ -387,13 +387,20 @@ DataFetcher> df1 = env -> { ``` ### How do I enable Chained DataLoaders? -As this changes execution order, you must opt-in to Chained DataLoaders by setting key-value pairs in `GraphQLContext`. +You must opt-in to Chained DataLoaders via `GraphQLUnusualConfiguration.DataloaderConfig`, as this may change order of dispatching. -1. Set `DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING` to `true` to enable Chained DataLoaders -2. Provide a `ScheduledExecutorService` to GraphQL Java, with key `DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY` and value implementing `DelayedDataLoaderDispatcherExecutorFactory` +1. Set `enableDataLoaderChaining(true)` to enable Chained DataLoaders +2. Provide a `ScheduledExecutorService` to GraphQL Java, with method `delayedDataLoaderExecutorFactory` and a parameter that implements `DelayedDataLoaderDispatcherExecutorFactory` + +For example +```java +GraphQL graphQL = GraphQL.unusualConfiguration(graphqlContext) + .dataloaderConfig() + .enableDataLoaderChaining(true); +``` ### What changed in GraphQL Java 25.0? -The DataFetcher in the example above, before version 25.0 would have caused execution to hang, because the first DataLoader ("name") was never dispatched. +The DataFetcher in the example above, before version 25.0 would have caused execution to hang, because the second DataLoader ("email") was never dispatched. Prior to version 25.0, users of GraphQL Java needed to manually dispatch DataLoaders to ensure execution completed. From version 25.0, the GraphQL Java engine will automatically dispatch Chained DataLoaders. From 66e93b997fde3880851b013af4d063f14b6545eb Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:08:04 +1000 Subject: [PATCH 3/5] Clarify example --- documentation/batching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/batching.md b/documentation/batching.md index 6fb3eb5..a4361a8 100644 --- a/documentation/batching.md +++ b/documentation/batching.md @@ -392,7 +392,7 @@ You must opt-in to Chained DataLoaders via `GraphQLUnusualConfiguration.Dataload 1. Set `enableDataLoaderChaining(true)` to enable Chained DataLoaders 2. Provide a `ScheduledExecutorService` to GraphQL Java, with method `delayedDataLoaderExecutorFactory` and a parameter that implements `DelayedDataLoaderDispatcherExecutorFactory` -For example +For example, to set `enableDataLoaderChaining`: ```java GraphQL graphQL = GraphQL.unusualConfiguration(graphqlContext) .dataloaderConfig() From 973c9154fd3943c017bb5c8ede403d2d5ef3e901 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:11:29 +1000 Subject: [PATCH 4/5] Add new unusual config way for batch window config --- documentation/batching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/batching.md b/documentation/batching.md index a4361a8..be52061 100644 --- a/documentation/batching.md +++ b/documentation/batching.md @@ -436,6 +436,6 @@ def barDF = { env -> By opting in to Chained DataLoaders, GraphQL Java will also calculate when to dispatch "delayed" DataLoaders. -The default value for the time to wait for these "delayed" DataLoaders is 500,000ns (`DEFAULT_BATCH_WINDOW_NANO_SECONDS_DEFAULT`). If you like, you can configure your own batch window by setting a key-value pair in `GraphQLContext`. Set `DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS` to be your preferred batch window size in nanoseconds. +The default value for the time to wait for these "delayed" DataLoaders is 500,000ns (`DEFAULT_BATCH_WINDOW_NANO_SECONDS_DEFAULT`). If you like, you can configure your own batch window via the method `delayedDataLoaderBatchWindowSize` in `GraphQLUnusualConfiguration.DataloaderConfig`. Note that the case, where one DataLoader depends on another DataLoader all within the same DataFetcher, is unaffected by this batch window configuration. This window configuration only changes how long to wait for the "delayed" DataLoader case, where a DataLoader depends on another async task. From 42627e584e5a8d8501b730a67ac4a75fb1dc5295 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Wed, 6 Aug 2025 06:54:34 +1000 Subject: [PATCH 5/5] Update documentation to remove scheduled executor and batch window --- documentation/batching.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/documentation/batching.md b/documentation/batching.md index be52061..6f7a3f3 100644 --- a/documentation/batching.md +++ b/documentation/batching.md @@ -389,8 +389,7 @@ DataFetcher> df1 = env -> { ### How do I enable Chained DataLoaders? You must opt-in to Chained DataLoaders via `GraphQLUnusualConfiguration.DataloaderConfig`, as this may change order of dispatching. -1. Set `enableDataLoaderChaining(true)` to enable Chained DataLoaders -2. Provide a `ScheduledExecutorService` to GraphQL Java, with method `delayedDataLoaderExecutorFactory` and a parameter that implements `DelayedDataLoaderDispatcherExecutorFactory` +Set `enableDataLoaderChaining(true)` to enable Chained DataLoaders. For example, to set `enableDataLoaderChaining`: ```java @@ -410,7 +409,7 @@ Note: The GraphQL Java engine can only optimally calculate DataLoader dispatches ### A special case: Delayed DataLoaders -In a previous code snippet we demonstrated one DataLoader depending on another DataLoader. +In a previous code snippet, we demonstrated one DataLoader depending on another DataLoader. Another special case is a "delayed" DataLoader, where a DataLoader depends on a slow async task instead. For example, here are two DataFetchers from [a test example](https://github.com/graphql-java/graphql-java/blob/master/src/test/groovy/graphql/ChainedDataLoaderTest.groovy): @@ -434,8 +433,4 @@ def barDF = { env -> } as DataFetcher ``` -By opting in to Chained DataLoaders, GraphQL Java will also calculate when to dispatch "delayed" DataLoaders. - -The default value for the time to wait for these "delayed" DataLoaders is 500,000ns (`DEFAULT_BATCH_WINDOW_NANO_SECONDS_DEFAULT`). If you like, you can configure your own batch window via the method `delayedDataLoaderBatchWindowSize` in `GraphQLUnusualConfiguration.DataloaderConfig`. - -Note that the case, where one DataLoader depends on another DataLoader all within the same DataFetcher, is unaffected by this batch window configuration. This window configuration only changes how long to wait for the "delayed" DataLoader case, where a DataLoader depends on another async task. +By opting in to Chained DataLoaders, GraphQL Java will also calculate when to dispatch "delayed" DataLoaders. These "delayed" DataLoaders will be enqueued for dispatch after the async task completes.