Dynamic schemas
We strongly recommend primarily using schema-first development. Most DGSs have a schema file and use the declarative, annotation-based programming model to create data fetchers and such. That said, there are scenarios where generating the schema from another source, possibly dynamically, is required.
Creating a schema from code¶
Create a schema from code by using the @DgsTypeDefinitionRegistry
annotation.
Use the @DgsTypeDefinitionRegistry
on methods inside a @DgsComponent
class to provide a TypeDefinitionRegistry
.
The TypeDefinitionRegistry
is part of the graphql-java API.
You use a TypeDefinitionRegistry
to programmatically define a schema.
Note that you can mix static schema files with one or more DgsTypeDefinitionRegistry
methods.
The result is a schema with all the registered types merged.
This way, you can primarily use a schema-first workflow while falling back to @DgsTypeDefinitionRegistry
to add some dynamic parts to the schema.
The following is an example of a DgsTypeDefinitionRegistry
.
@DgsComponent
public class DynamicTypeDefinitions {
@DgsTypeDefinitionRegistry
public TypeDefinitionRegistry registry() {
TypeDefinitionRegistry typeDefinitionRegistry = new TypeDefinitionRegistry();
ObjectTypeExtensionDefinition query = ObjectTypeExtensionDefinition.newObjectTypeExtensionDefinition().name("Query").fieldDefinition(
FieldDefinition.newFieldDefinition().name("randomNumber").type(new TypeName("Int")).build()
).build();
typeDefinitionRegistry.add(query);
return typeDefinitionRegistry;
}
}
This TypeDefinitionRegistry
creates a field randomNumber
on the Query
object type.
@DgsComponent
public class DynamicTypeDefinitions {
@DgsTypeDefinitionRegistry
public TypeDefinitionRegistry registry(TypeDefinitionRegistry registry) {
TypeDefinitionRegistry typeDefinitionRegistry = new TypeDefinitionRegistry();
...
typeDefinitionRegistry.add(query);
return typeDefinitionRegistry;
}
}
You can also pass in an existing TypeDefinitionRegistry
as a parameter in case you need access to existing types.
Creating datafetchers programmatically¶
If you're creating schema elements dynamically, it's likely you also need to create datafetchers dynamically. You can use the @DgsCodeRegistry
annotation to add datafetchers programmatically.
A method annotated @DgsCodeRegistry
takes two arguments:
GraphQLCodeRegistry.Builder codeRegistryBuilder TypeDefinitionRegistry registry
The method must return the modified GraphQLCodeRegistry.Builder.
The following is an example of a programmatically created datafetcher for the field created in the previous example.
@DgsComponent
public class DynamicDataFetcher {
@DgsCodeRegistry
public GraphQLCodeRegistry.Builder registry(GraphQLCodeRegistry.Builder codeRegistryBuilder, TypeDefinitionRegistry registry) {
DataFetcher<Integer> df = (dfe) -> new Random().nextInt();
FieldCoordinates coordinates = FieldCoordinates.coordinates("Query", "randomNumber");
return codeRegistryBuilder.dataFetcher(coordinates, df);
}
}
Changing schemas at runtime¶
It's helpful to combine creating schemas/datafetchers at runtime with dynamically re-loading the schema in some very rare use-cases.
You can achieve this by implementing your own ReloadSchemaIndicator
.
You can use an external signal (e.g., reading from a message queue) to have the framework recreate the schema by executing the @DgsTypeDefinitionRegistry
and @DgsCodeRegistry
again.
If these methods create the schema based on external input, you have a system that can dynamically rewire its API.
For obvious reasons, this isn't an approach that you should use for typical APIs; stable APIs are generally the thing to aim for!