GraphQL Federation

Elide supports GraphQL Federation. This feature needs to be enabled to be used.

Enabling GraphQL Federation

elide:
  graphql:
    federation:
      enabled: true

Schema Introspection Queries

When GraphQL Federation is enabled, Elide will respond to enhanced introspection queries with Query._service with the GraphQL schemas generated by Elide.

query {
  _service {
    sdl
  }
}

Elide does not have any built in measures to control which clients can execute the introspection queries. These queries should typically be restricted only to the federated graph routers.

Implementing Federated Graphs

Elide generates its GraphQL schema programatically and cannot be used to define federated entities.

This will need to be done in another subgraph implementation using a different subgraph library, for instance Spring GraphQL.

Extending an Elide entity

The Elide entity can be extended with additional entities from the subgraph using the @extends directive.

This configurations are done in the subgraph and not in Elide.

In the following example the Group entity from Elide is being extended to provide the additional GroupReview entity provided by the subgraph.

type Group @key(fields: "name") @extends {
    name: DeferredID! @external
    groupReviews: [GroupReview!]!
}

Note that Elide uses a custom scalar DeferredID instead of ID which will need to be registered with the subgraph.

The following query is an example that starts from the Group entity on Elide and references the GroupReview entity on the subgraph.

query {
  group {
    edges {
      node {
        commonName
        groupReviews {
          stars
          text
        }
      }
    }
  }
}

After the router queries the Group entity on Elide, it will also make another query to this subgraph to get the GroupReview entity.

The router will use the following query on the subgraph to add the additional fields of GroupReview to the Group entity.

query {
  _entities(representations: [{__typename: "Group", name: "com.yahoo.elide"}]) {
    ... on Group {
      stars
      text
    }
  }
}

For Spring GraphQL the representations can be configured as shown below.

The mapping for the representations to the Group is configured in the entity data fetcher for instance in com.example.reviews.config.GraphQLConfiguration.

DataFetcher<?> entityDataFetcher = env -> {
  List<Map<String, Object>> representations = env.getArgument(_Entity.argumentName);
  return representations.stream().map(representation -> {
    // Assume only a single id key and no composite keys
    String idKey = representation.keySet().stream().filter(key -> !"__typename".equals(key)).findFirst()
        .orElse(null);
    String id = (String) representation.get(idKey);
    if (GROUP_TYPE.equals(representation.get("__typename"))) {
      return new Group(id);
    }
    return null;
  }).toList();
};

Including Elide entities

The subgraph entity can include Elide entities as Elide supports the @key directive.

The following is the schema that Elide generates for the Group entity.

type Group @key(fields : "name") {
  commonName: String
  description: String
  name: DeferredID
  products(after: String, data: [ProductInput], filter: String, first: String, ids: [String], op: ElideRelationshipOp = FETCH, sort: String): ProductConnection
}

The following is the schema of GroupReview on the subgraph.

type GroupReview {
    id: ID!,
    text: String
    stars: Int!
    group: Group
}

The following query is an example that starts from the GroupReview entity on subgraph and references the Group entity on Elide.

query {
  groupReviews {
    id
    stars
    text
    group {
      name
      commonName
    }
  }
}

After calling to retrieve the GroupReview entites on the subgraph, the router calls Elide with the following query.

query {
  _entities(representations: [{__typename: "Group", name: "com.yahoo.elide"}]) {
    ... on Group {
      name
      commonName
    }
  }
}

Elide will determine the projection in GraphQLEntityProjectionMaker.

The EntitiesDataFetcher will fetch a list of NodeContainer.

public class EntitiesDataFetcher implements DataFetcher<List<NodeContainer>> {
  ...
}

The EntityTypeResolver will map the NodeContainer to the appropriate GraphQLObjectType.

Defining the DeferredID scalar

Elide uses a custom scalar DeferredID instead of ID.

This needs to be registered with the subgraph implementation.

The following is the schema definition.

scalar DeferredID

For Spring GraphQL this can be configured as shown below.

The following is the Java code for the GraphQLScalarType.

public class GraphQLScalars {
  public static GraphQLScalarType DEFERRED_ID = GraphQLScalarType.newScalar().name("DeferredID")
      .description("The DeferredID scalar type represents a unique identifier.")
      .coercing(new Coercing<Object, String>() {
        @Override
        public String serialize(Object o) {
          return o.toString();
        }

        @Override
        public String parseValue(Object o) {
          return o.toString();
        }

        @Override
        public String parseLiteral(Object o) {
          if (o instanceof StringValue stringValue) {
            return stringValue.getValue();
          }
          if (o instanceof IntValue intValue) {
            return intValue.getValue().toString();
          }
          return o.toString();
        }
      }).build();
}

The following is the Java code for registering the scalar in GraphQLConfiguration.

@Bean
public GraphQlSourceBuilderCustomizer graphqlSourceBuilderCustomizer() {
  return schemaResourceBuilder -> schemaResourceBuilder
    .configureRuntimeWiring(runtimeWiring -> runtimeWiring.scalar(GraphQLScalars.DEFERRED_ID));
}