Elide supports GraphQL Federation. This feature needs to be enabled to be used.
elide:
graphql:
federation:
enabled: true
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.
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.
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();
};
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
.
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));
}