Skip to content

Add nested key entity support for GraphQL Federation#1476

Open
JBodkin-Amphora wants to merge 3 commits into
spring-projects:mainfrom
JBodkin-Amphora:fix/federation-nested-key-entity
Open

Add nested key entity support for GraphQL Federation#1476
JBodkin-Amphora wants to merge 3 commits into
spring-projects:mainfrom
JBodkin-Amphora:fix/federation-nested-key-entity

Conversation

@JBodkin-Amphora

@JBodkin-Amphora JBodkin-Amphora commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

This pull request adds support for nested keys in GraphQL Federation when using the EntityMapping annotation. Additionally, isOmitted is always set to false for entity mapping, as the GraphQL Gateway will not send requests when the object is null

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 8, 2026
Signed-off-by: James Bodkin <james.bodkin@amphora.net>
@JBodkin-Amphora JBodkin-Amphora force-pushed the fix/federation-nested-key-entity branch from 436ea02 to d0ce0fb Compare June 8, 2026 14:13
Signed-off-by: James Bodkin <james.bodkin@amphora.net>
@JBodkin-Amphora JBodkin-Amphora force-pushed the fix/federation-nested-key-entity branch from c7b49dc to c09b84b Compare June 30, 2026 10:48
@JBodkin-Amphora

Copy link
Copy Markdown
Contributor Author

@bclozel Would it be possible to get some feedback on this PR?

@rstoyanchev rstoyanchev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JBodkin-Amphora you're proposing the binding to be done from the full representation map. Is that based on something concrete, and is it not possible for example that it might be a submap under a specific key?

@JBodkin-Amphora

Copy link
Copy Markdown
Contributor Author

@rstoyanchev The representation map is populated by the GraphQL Federation Gateway based on the @keys annotation, which accepts a field set as the value. We're seeing an issue internally where the federation is failing when the annotation value has multiple fields or nested fields.

There are two unit tests in this PR that show the different field sets:

  • type Library @key(fields: "id location { id }")
  • type LocationArea @key(fields: "location { id }")

The first case is easy to identify, as the representation map will contain 3 fields: id, location and __typename.

The second case is more difficult to identify, as it will contain location and __typename. We need to be able to differentiate this against a field set such as @key(fields: "id"), hence the check to see if the value is a scalar value or a class. (Thinking aloud, rather than checking for a scalar value, maybe it would be better to check for a Map?).

Since the argument binder already supports recursion, further nested keys should be supported already.

@JBodkin-Amphora

Copy link
Copy Markdown
Contributor Author

I have some concerns about the approach, even changing the checking to a map doesn't help in the instance of null values. I've been playing around locally and this seems to be better in my mind and matches against what the apollo federation js library does.

	private boolean isScalarValue(GraphQLSchema schema, Map<String, Object> representation) {
		final var typeName = Objects.requireNonNull((String) representation.get("__typename"));
		final var type = schema.getType(typeName);
		if (!(type instanceof GraphQLDirectiveContainer container)) {
			return false;
		}

		final var keyDirective = container.getAppliedDirective("key");
		final var fields = keyDirective.getArgument("fields").<String>getValue();

		final var parser = new Parser();
		final var document = parser.parseDocument("{" + fields + "}");

		final var operationDefinition = (OperationDefinition) document.getDefinitions().get(0);
		final var selectionSet = operationDefinition.getSelectionSet();
		if (selectionSet.getSelections().size() > 1) {
			return false;
		}

		final var field = (Field) selectionSet.getSelections().get(0);
		return field.getSelectionSet() == null || field.getSelectionSet().getSelections().isEmpty();
	}

It needs some defensive programming and probably caching as we don't want to parse the fields on every request.

Signed-off-by: James Bodkin <james.bodkin@amphora.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants