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 dataloaderhas seen a data item before, it will have cached the value and will return it without having to ask for it again.
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'snames. It is likely that many of these people will be friends in common.
As graphql descends each level of the query (e.g., as it processes hero and then friends and then for each of their friends),the data loader is called to "promise" to deliver a person object. At each level dataloader.dispatch() will becalled to fire off the batch requests for that part of the query. With caching turned on (the default) thenany previously returned person will be returned as-is for no cost.
In the above example there are only 5 unique people mentioned but with caching and batching retrieval in place there will be only3 calls to the batch loader function. 3 calls over the network or to a database is much better than 15 calls, you will agree.
If you use capabilities like java.util.concurrent.CompletableFuture.supplyAsync() then you can make it even more efficient by making theremote calls asynchronous to the rest of the query. This will make it even more timely since multiple calls can happen at onceif need be.
The only execution that works with DataLoader is graphql.execution.AsyncExecutionStrategy. This is because this execution strategy knowswhen the most optimal time to dispatch() your load calls is. It does this by deeply tracking how many fields are outstanding and whether theyare list values and so on.
Other execution strategies such as ExecutorServiceExecutionStrategy can't do this and hence if the data loader code detectsyou are not using AsyncExecutionStrategy then it will simply dispatch the data loader as each field is encountered. Youmay get caching of values, but you will not get batching of them.
If 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 tocache data meant for user A to then later give it to user B in a subsequent request.
The scope of your DataLoader instances is important. You will want to create them per web request toensure data is only cached within that web request and no more. It also ensures that a dispatch callonly affects that graphql execution and no other.
DataLoaders by default act as caches. If they have seen a value before for a key then they will automatically returnit in order to be efficient. They cache promises to a value and optionally the value itself.
graphql-java tracks what outstanding data loader calls have been made, and it is its responsibility to call dispatchin the background at the most optimal time, which is when all graphql fields have been examined and dispatched.
However, there is a code pattern that will cause your data loader calls to never complete, and these MUST be avoided. This badpattern consists of making an asynchronous off thread call to a DataLoader in your data fetcher.
In the example above, the call to characterDataLoader.load(argId) can happen some time in the future on another thread. The graphql-javaengine has no way of knowing when it's a good time to dispatch outstanding DataLoader calls and hence the data loader call might never completeas expected and no results will be returned.
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 batchedtogether. The most optimal time is when the graphql field tree has been examined and all field values are currently dispatched.
Then later when the DataLoader is dispatched, its BatchLoader function is called. This code can be asynchronous so that if you have multiple batch loaderfunctions they all can run at once. In the code above CompletableFuture.supplyAsync(() -> getTheseCharacters(keys)); will run the getTheseCharactersmethod in another thread.
The data loader library supports two types of context being passed to the batch loader. The first isan overall context object per dataloader, and the second is a map of per loaded key context objects.
This allows you to pass in the extra details you may need to make downstream calls. The dataloader key is usedin the caching of results, but the context objects can be made available to help with the call.
So, do I really need a 32-bit Java version next to my 64-bit Java version? Is that even possible? Do I need to set up something else? I need the 64-bit version for other apps, I cannot replace it with a 32-bit version (provided that's even possible with 64-bit Win 7).
And I didn't even have to change JAVA_HOME to get the data loader working, it just works now. Automagically. Not even PATH was changed by the installer. java -version still reports the 64-bit version, but somehow data loader can find the 32-bit version (I guess because it was installed in its default folder).
It would be much more efficient to create a list of directors to load, and load all of them at once in a single call.This first of all must be supported by the Director service, because that service needs to provide a way to load a list of Directors.The data fetchers in the Movie service need to be smart as well, to take care of batching the requests to the Directors service.
The easiest way for you to register a data loader is for you to create a class that implements the org.dataloader.BatchLoader or org.dataloader.MappedBatchLoader interface.This interface is parameterized; it requires a type for the key and result of the BatchLoader.For example, if the identifiers for a Director are of type String, you could have a org.dataloader.BatchLoader.You must annotate the class with @DgsDataLoader so that the framework will register the data loader it represents.
The data loader is responsible for loading data for a given list of keys.In this example, it just passes on the list of keys to the backend that owns Director (this could for example be a [gRPC] service).However, you might also write such a service so that it loads data from a database.Although this example registers a data loader, nobody will use that data loader until you implement a data fetcher that uses it.
If you want to handle exceptions during fetching of partial results, you can return a list of Try objects from the loader. The query result will contain partial results for the successful calls and an error for the exception case.
The BatchLoader interface creates a List of values for a List of keys.You can also use the MappedBatchLoader which creates a Map of key/values for a Set of values.The latter is a better choice if you do not expect all keys to have a value.You register a MappedBatchLoader in the same way as you register a BatchLoader:
You can also get the data loader in a type-safe way by using our custom DgsDataFetchingEnvironment, which is an enhanced version of DataFetchingEnvironment in graphql-java, and provides getDataLoader() using the classname.
The same works if you have @DgsDataLoader defined as a lambda instead of on a class as shown here.If you have multiple @DgsDataLoader lambdas defined as fields in the same class, you won't be able to use this feature.It is recommended that you use getDataLoader() with the loader name passed as a string in such cases.
Note that there is no logic present about how batching works exactly; this is all handled by the framework!The framework will recognize that many directors need to be loaded when many movies are loaded, batch up all the calls to the data loader, and call the data loader with a list of IDs instead of a single ID.The data loader implemented above already knows how to handle a list of IDs, and that way it avoids the N+1 problem.
All the *async* methods on CompletableFuture allow you to provide an Executor.If provided, that stage of the CompletableFuture will run on a Thread represented by that Executor.If no Executor is provided, the stage runs on a Thread from the fork/join pool.
In Spring WebMVC it's common to store request information such as SSO data on ThreadLocal, which doesn't automatically become available if you run on a different thread, in this case a thread from the Executor.Spring Security uses ThreadLocal for example to store its SecurityContext.
While creating and extending an Executor with the correct context propagation goes beyond the scope of this documentation, a good place to start is the DelegatingSecurityContextExecutor from Spring Security.A relevant howto guide is available here.
The above implementation will hang indefinitely in the call to the second data loader in the chain.The java-dataloader library already handles dispatching of data loader calls at each level of the query. However, when there is yet another load call to the same or different dataloader, who would call dispatch on this data loader?Since these return Completable Futures, it is not easy to determine when the dispatch should be triggered.To handle this, we need to explicitly call dispatch on the second data loader (shown below) , since there is nothing else to trigger the dispatch when load is invoked on dataloaderB. @DgsData(parentType = "Query", field = "messageFromBatchLoader") public CompletableFuture getMessage(DataFetchingEnvironment env) DataLoader dataLoader = env.getDataLoader("messages"); DataLoader dataLoaderB = env.getDataLoader("greetings"); return dataLoader.load("a").thenCompose(key -> CompletableFuture loadA = dataLoaderB.load(key); // Manually call dispatch dataLoaderB.dispatch(); return loadA; ); This is expected according to the documented behavior here.However, this can result in suboptimal batching for dataloaderB, with a bacth size of 1.
The v8.1.0 release introduces a new feature to enable ticker mode available in the java-dataloader libraryThis allows you to schedule the dispatch checks instead of manually calling dispatch in your data loaders.By default, the checks will occur every 10ms but can be configured via dgs.graphql.dataloader.scheduleDuration.To enable ticker mode in the DGS framework, you can set dgs.graphql.dataloader.ticker-mode-enabled to true.In addition, you can also specify dispatch predicates per dataloader vi a@DgsDispatchPredicateto better control the batching.
d3342ee215