Skip to content

Local GraphQLContext doesn't propagate values when spring actuator is active #1142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DoodlesOnMyFood opened this issue Mar 4, 2025 · 9 comments
Assignees
Labels
status: invalid An issue that we don't feel is valid

Comments

@DoodlesOnMyFood
Copy link

DoodlesOnMyFood commented Mar 4, 2025

On my controllers my code propagates values via local context, so something like this.

@SchemaMapping(typeName = "Foo", field = "bar")
public List<Bar> getBars(DataFetchingEnvironment env) {
var localContext = (GraphQLContext) Objects.requireNonNull(env.getLocalContext());
...
localContext.put("BAR_ID", service.getValue(bar.getId()));
...
}

@SchemaMapping(typeName = "Bar", field = "name")
public String getBarName(DataFetchingEnvironment env) {
var localContext = (GraphQLContext) Objects.requireNonNull(env.getLocalContext());
...
var barId = (String) localContext.get("BAR_ID");
...
}

Which worked fine for my use-case.

But I noticed that my code breaks when I activate spring-actuator.

To elaborate, the local context would no longer have my values, and only contain 'micrometer.observation' values.

var barId = (String) localContext.get("BAR_ID"); // null

I read the documentations regarding contexts and observability but failed to find a fix.

I was wondering if this was intentional and I need some additional configuration to make my code work.

I haven't tested with global context values.

@DoodlesOnMyFood DoodlesOnMyFood changed the title Local GraphQLContext doesn't propagate when spring actuator is active Local GraphQLContext doesn't propagate values when spring actuator is active Mar 4, 2025
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 4, 2025
@bclozel
Copy link
Member

bclozel commented Mar 4, 2025

Which Spring GraphQL version are you using? This looks like a duplicate of #761

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Mar 4, 2025
@DoodlesOnMyFood
Copy link
Author

I'm on version 1.3.3

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 5, 2025
@bclozel bclozel self-assigned this Mar 5, 2025
@bclozel
Copy link
Member

bclozel commented Mar 8, 2025

Something looks wrong with your code snippet. It's assuming that a local context exists and adds to it, whereas I believe that a new local context is supposed to be returned with the data fetcher result for the next data fetchers to consider.

I have created a sample application and I'm not seeing any failure.

Here's a RuntimeWiringConfigurer that contributes a data fecher with a new localcontext:

package org.example.graphqlcontext;

import graphql.GraphQLContext;
import graphql.execution.DataFetcherResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;

@Configuration
public class GraphQlConfiguration {

	@Bean
	public RuntimeWiringConfigurer runtimeWiringConfigurer() {
		return runtimeWiring -> {
			runtimeWiring.type("Query", typeBuilder ->
					typeBuilder.dataFetcher("bookById", environment -> {
						GraphQLContext localContext = GraphQLContext.newContext()
								.put("project", "spring-graphql")
								.build();
						return DataFetcherResult.newResult()
								.data(new Book("test book", 42L))
								.localContext(localContext)
								.build();
					}));
		};
	}
}

As you can see, the schema mapping handler expects and finds a value in the local context:

package org.example.graphqlcontext;

import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;

import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;

@Controller
public class TestController {

	@SchemaMapping
	public Author author(Book book, DataFetchingEnvironment environment) {
		GraphQLContext localContext = (GraphQLContext) environment.getLocalContext();
		String project = (String) localContext.get("project");
		Assert.isTrue(project == "spring-graphql", "missing value from local context");
		return new Author(book.authorId(), "Test author");
	}

}

This is for the following schema:

type Query {
    bookById(id: ID!): Book
}

type Book {
    title: String
    author: Author
}

type Author {
    name: String
}

I have tested this with and without actuator on the classpath.

Please share a minimal sample application that reproduces the problem. Maybe your assumptions about the GraphQL local context are wrong in the first place?

@bclozel bclozel added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Mar 8, 2025
@DoodlesOnMyFood
Copy link
Author

In my setup, I create and inject a context in a filter. Maybe this is an anti-pattern?

Anyways, I'll try to reproduce my issue on a smaller demo during the weekend. Thanks for the feedback.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 11, 2025
@bclozel bclozel added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Mar 12, 2025
@spring-projects-issues
Copy link
Collaborator

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Mar 19, 2025
@spring-projects-issues
Copy link
Collaborator

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

@spring-projects-issues spring-projects-issues added status: waiting-for-triage An issue we've not yet triaged and removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue status: waiting-for-triage An issue we've not yet triaged labels Mar 26, 2025
@bclozel bclozel closed this as not planned Won't fix, can't repro, duplicate, stale Mar 26, 2025
@bclozel bclozel added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 26, 2025
@DoodlesOnMyFood
Copy link
Author

Sorry for taking so long.

Here is my demo that illustrates my problem.

demo.zip

My query.

query {
    svc(id: 1) {
        images {
            name
            artifacts {
                digest
            }
        }
    }
}

The result will be the same, but I printed out a simple line.

// Without spring-boot-starter-actuator
InfraId is 1
// With spring-boot-starter-actuator
InfraId is null

@bclozel
Copy link
Member

bclozel commented Apr 1, 2025

Thanks for the repro, I can see now the problem.

In graphql-java, the main GraphQL context is shared for the entire lifetime of the request. On the other hand, a local context is only local to the child data fetching operations. If you would like to provide a local context to the data fetching operations under the current one, you'll need to return a DataFetcherResult from your controller method with a new local context, like this:

    @SchemaMapping(typeName = "Query", field = "svc")
    public DataFetcherResult<Svc> getSvc(DataFetchingEnvironment env, @Argument("id") Integer id) {
        var infra = 1;
        DataFetcherResult.Builder<Svc> resultBuilder = DataFetcherResult.newResult();
        Svc svcName = new Svc().setName("svcName").setId(id);
        if (env.getSelectionSet().contains("images")) {
            GraphQLContext context = GraphQLContext.of(Map.of("svcId", id, "infraId", infra));
            resultBuilder.localContext(context);
        }
        return resultBuilder.data(svcName).build();
    }

I've opened #1167 to see how we can document this and maybe consider an improvement to make this use case easier.

Note: I do see a behavior difference with actuator vs. without actuator, but I have been debugging our GraphQlObservationInstrumentation and I am not seeing the localContext with your custom values when it hits the instrumentation. Maybe there is a slight behavior difference with/without instrumentations in graphql-java.

@DoodlesOnMyFood
Copy link
Author

Thanks, your example works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants