graphql-java/graphql-java-spring

Request-scoped DataLoaderRegistry blows up

kijanowski opened this issue · 6 comments

I have given a try the code introduced by #12
I used version 1.0 and created a CustomGraphQLInvocation class based on https://github.com/graphql-java/graphql-java-spring/pull/12/files#diff-cb93f026b7b911f1cdfa731e56caa39f

The comment in this PR says:
I think it's overkill to create a ExecutionInputCustomizer for this, simply defining a (request-scoped) @bean should be sufficient.

I've create that bean like this:

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public DataLoaderRegistry dataLoaderRegistry() {...}

And it fails with:

{
  "timestamp": "2019-09-29T11:07:42.456+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "Error creating bean with name 'scopedTarget.dataLoaderRegistry': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.",
  "path": "/graphql"
}

If I change the scope to WebApplicationContext.SCOPE_APPLICATION then it doesn't throw an error. How should this be approached to have a request scoped DataLoaderRegistry?

A working workaround is to change the DefaultGraphQLInvocation and remove:

    @Autowired(required = false)
    DataLoaderRegistry dataLoaderRegistry;

and replace:

        if (dataLoaderRegistry != null) {
            executionInputBuilder.dataLoaderRegistry(dataLoaderRegistry);
        }

with:

        DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry();

        // Loader specific configuration
        DataLoader<String, CountryTO> countryLoader = DataLoader.newDataLoader(dataFetchers.countryBatchLoader());
        dataLoaderRegistry.register("countries", countryLoader);
        // Loader specific configuration end
        
        executionInputBuilder.dataLoaderRegistry(dataLoaderRegistry);
        executionInputBuilder.context(dataLoaderRegistry); // add it to the context to use it later in fetchers

I think we need to remove the DataLoaderRegistry from the DefaultGraphQLInvocation. Spring Request-scoped beans will not work when creating new threads.

I think it's better to create a custom ExecutionInputCustomizer which creates the DataLoaderRegistry and attaches it to the ExecutionInput.

Can I ask the status of this? Is it possible to configure the DataLoaderRegistry to be request scoped as things stand?

Hello,
I had this working with request scoped dataloader, by removing the async. Not great, but at least I can have batch compute and avoid the 1+N request issue.
For this, I simply use return CompletableFuture.completedFuture(value); in the DataLoader load method.
This way, my loader function can access all request scoped beans that are needed.

We can user Spring Async Executor Framework for DataLoaderRegistery

This project is now archived in favor of the official Spring GraphQL integration.