Skip to content

Federated entities marked with resolvable:false should not be reported as unmapped #1222

Closed as not planned
@joshuapaulallen

Description

@joshuapaulallen

Hi! Big spring-graphql fan here. Thanks for building such great GraphQL tooling. I have a question related to federation.

Context

I work with a handful of Spring Boot apps that use GraphQL federation. An app's subgraph may contain references to types owned by a different subgraph, as described here. To borrow an example from the spring-graphql federation tests, the subgraph that owns a Book type may contain a reference to a different subgraph that owns and knows more details about a Publisher type.

type Book @key(fields: "id") @extends {
    id: ID! @external
    author: Author
    publisher: Publisher
}

type Publisher @key(fields: "id", resolvable: false) {
    id: ID! @external
}

Possible Problem

After upgrading to spring-graphql 1.4.0, we began seeing a failure on application startup that might look like this:

java.lang.IllegalStateException: Failed to load ApplicationContext for ...
	...
Caused by: java.lang.IllegalStateException: Unmapped entity types: 'Publisher'
	at org.springframework.graphql.data.federation.FederationSchemaFactory.checkEntityMappings(FederationSchemaFactory.java:202)
	at org.springframework.graphql.data.federation.FederationSchemaFactory.createSchemaTransformer(FederationSchemaFactory.java:183)
	...

I believe this is related to a new feature described by #1088. I support failing fast due to a missing @EntityMapping method rather than risking runtime failures. However, I believe it is legitimate that types with the @key(..., resolvable: false) directive would not have an @EntityMapping method for that type.

Our workaround is to somewhat artificially provide mapping methods that return stubs representing those references, something like this:

@Controller
public class UnresolvableReferenceController {

  public record Publisher(String id) {}

  @EntityMapping
  public Publisher publisher(String id) {
    return new Publisher(id);
  }
  
}

Possible Solution

Perhaps unresolvable types should be exempt from the check that's involved here? I've been playing with a solution in FederationSchemaFactory that looks like the code below. However, I'm certainly open to your guidance if this wouldn't make sense.

private void checkEntityMappings(TypeDefinitionRegistry registry) {
    List<String> unmappedEntities = new ArrayList<>();
    for (TypeDefinition<?> type : registry.types().values()) {
      if (isEntityMappingExpected(type) && !this.handlerMethods.containsKey(type.getName())) {
        unmappedEntities.add(type.getName());
      }
    }
    if (!unmappedEntities.isEmpty()) {
      throw new IllegalStateException("Unmapped entity types: " +
                                      unmappedEntities.stream().collect(Collectors.joining("', '", "'", "'")));
    }
  }

  /**
   * Determine if a handler method is expected for this type: there is at least one '@key' directive
   * whose 'resolvable' argument evaluates to true (either explicitly, or if the argument is not set).
   *
   * @param type The type to inspect.
   * @return true if a handler method is expected for this type
   */
  private boolean isEntityMappingExpected(TypeDefinition<?> type) {
    List<Directive> keyDirectives = type.getDirectives("key");
    return !keyDirectives.isEmpty() && keyDirectives.stream()
        .anyMatch(keyDirective -> {
          Argument resolvableArg = keyDirective.getArgument("resolvable");
          return resolvableArg == null ||
                 (resolvableArg.getValue() instanceof BooleanValue) && ((BooleanValue) resolvableArg.getValue()).isValue();
        });
  }

Thank you in advance for your consideration!

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions