Simplify GraphQlSource auto-configuration

This commit simplifies the `GraphQL` configuration by focusing the
infrastructure around the `GraphQlSource` auto-configuration.

With this commit, the auto-configuration contributes a single
`GraphQlSource` bean and removes the previoulsy contributed beans:
`GraphQlSource.Builder` and `RuntimeWiring`.

If a developer wants to take full control over the GraphQL setup, then
contributing a `GraphQlSource` bean is the easiest solution.

This commit also improves the `GraphQlSource.Builder` and allows for
customizing the `RuntimeWiring.Builder` instead of providing a direct
instance. This aligns well with the `RuntimeWiringBuilderCustomizer`
interface provided by the starter.

Closes gh-101
This commit is contained in:
Brian Clozel
2021-07-23 09:54:17 +02:00
parent 11fdb43238
commit 5c7b809aa3
8 changed files with 48 additions and 67 deletions

View File

@@ -24,7 +24,6 @@ import java.util.stream.Collectors;
import graphql.GraphQL;
import graphql.execution.instrumentation.Instrumentation;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -51,50 +50,33 @@ import org.springframework.graphql.execution.GraphQlSource;
@EnableConfigurationProperties(GraphQlProperties.class)
public class GraphQlAutoConfiguration {
private static final String[] SCHEMA_FILES_EXTENSIONS = new String[] {"*.graphqls", "*.graphql", "*.gql", "*.gqls"};
@Bean
public GraphQlSource graphQlSource(GraphQlSource.Builder builder) {
public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties,
ObjectProvider<DataFetcherExceptionResolver> exceptionResolversProvider,
ObjectProvider<Instrumentation> instrumentationsProvider,
ObjectProvider<GraphQlSourceBuilderCustomizer> sourceCustomizers,
ObjectProvider<RuntimeWiringBuilderCustomizer> wiringCustomizers) throws IOException {
List<Resource> schemaResources = resolveSchemaResources(resourcePatternResolver, properties.getSchema().getLocations());
GraphQlSource.Builder builder = GraphQlSource.builder().schemaResources(schemaResources.toArray(new Resource[0]))
.exceptionResolvers(exceptionResolversProvider.orderedStream().collect(Collectors.toList()))
.instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList()));
wiringCustomizers.orderedStream().forEach((customizer) -> builder.runtimeWiring(customizer::customize));
sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(GraphQlSource.Builder.class)
public static class GraphQlSourceConfiguration {
private static final String[] SCHEMA_FILES_EXTENSIONS = new String[] {"*.graphqls", "*.graphql", "*.gql", "*.gqls"};
@Bean
@ConditionalOnMissingBean
public RuntimeWiring runtimeWiring(ObjectProvider<RuntimeWiringBuilderCustomizer> customizers) {
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean
public GraphQlSource.Builder graphQlSourceBuilder(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties,
RuntimeWiring runtimeWiring, ObjectProvider<DataFetcherExceptionResolver> exceptionResolversProvider,
ObjectProvider<Instrumentation> instrumentationsProvider,
ObjectProvider<GraphQlSourceBuilderCustomizer> customizersProvider) throws IOException {
List<Resource> schemaResources = resolveSchemaResources(resourcePatternResolver, properties.getSchema().getLocations());
GraphQlSource.Builder builder = GraphQlSource.builder().schemaResources(schemaResources.toArray(new Resource[0]))
.runtimeWiring(runtimeWiring)
.exceptionResolvers(exceptionResolversProvider.orderedStream().collect(Collectors.toList()))
.instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList()));
customizersProvider.forEach((customizer) -> customizer.customize(builder));
return builder;
}
private List<Resource> resolveSchemaResources(ResourcePatternResolver resolver, List<String> schemaLocations) throws IOException {
List<Resource> schemaResources = new ArrayList<>();
for (String location : schemaLocations) {
for (String extension : SCHEMA_FILES_EXTENSIONS) {
schemaResources.addAll(Arrays.asList(resolver.getResources(location + extension)));
}
private List<Resource> resolveSchemaResources(ResourcePatternResolver resolver, List<String> schemaLocations) throws IOException {
List<Resource> schemaResources = new ArrayList<>();
for (String location : schemaLocations) {
for (String extension : SCHEMA_FILES_EXTENSIONS) {
schemaResources.addAll(Arrays.asList(resolver.getResources(location + extension)));
}
return schemaResources;
}
return schemaResources;
}
}

View File

@@ -50,7 +50,7 @@ import org.springframework.graphql.execution.GraphQlSource;
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass({GraphQL.class, QuerydslPredicateExecutor.class })
@ConditionalOnBean(GraphQlSource.Builder.class)
@ConditionalOnBean(GraphQlSource.class)
@AutoConfigureAfter(GraphQlAutoConfiguration.class)
public class GraphQlWebFluxQuerydslAutoConfiguration {

View File

@@ -50,7 +50,7 @@ import org.springframework.graphql.execution.GraphQlSource;
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({GraphQL.class, QuerydslPredicateExecutor.class })
@ConditionalOnBean(GraphQlSource.Builder.class)
@ConditionalOnBean(GraphQlSource.class)
@AutoConfigureAfter(GraphQlAutoConfiguration.class)
public class GraphQlWebMvcQuerydslAutoConfiguration {

View File

@@ -51,7 +51,8 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
private final List<Resource> schemaResources = new ArrayList<>();
private RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
private Consumer<RuntimeWiring.Builder> runtimeWiringConfigurers = (builder) -> {
};
private final List<DataFetcherExceptionResolver> exceptionResolvers = new ArrayList<>();
@@ -70,9 +71,9 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
}
@Override
public GraphQlSource.Builder runtimeWiring(RuntimeWiring runtimeWiring) {
Assert.notNull(runtimeWiring, "RuntimeWiring is required");
this.runtimeWiring = runtimeWiring;
public GraphQlSource.Builder runtimeWiring(Consumer<RuntimeWiring.Builder> configurer) {
Assert.notNull(configurer, "RuntimeWiring configurer is required");
this.runtimeWiringConfigurers = this.runtimeWiringConfigurers.andThen(configurer);
return this;
}
@@ -106,7 +107,9 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
.map(this::parseSchemaResource).reduce(TypeDefinitionRegistry::merge)
.orElseThrow(() -> new IllegalArgumentException("'schemaResources' should not be empty"));
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, this.runtimeWiring);
RuntimeWiring.Builder runtimeWiring = RuntimeWiring.newRuntimeWiring();
this.runtimeWiringConfigurers.accept(runtimeWiring);
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, runtimeWiring.build());
schema = applyTypeVisitors(schema);
GraphQL.Builder builder = GraphQL.newGraphQL(schema);

View File

@@ -79,13 +79,15 @@ public interface GraphQlSource {
Builder schemaResources(Resource... resources);
/**
* Set a {@link RuntimeWiring} to contribute data fetchers and more.
* @param runtimeWiring the runtime wiring for contribution
* Configure consumers that will be given access to the {@link RuntimeWiring.Builder} used to
* build the {@link RuntimeWiring}. A {@link RuntimeWiring#newRuntimeWiring() default builder}
* instance is created as a starting point.
* @param configurer the runtime wiring configurer
* @return the current builder
* @see graphql.schema.idl.SchemaGenerator#makeExecutableSchema(TypeDefinitionRegistry,
* RuntimeWiring)
*/
Builder runtimeWiring(RuntimeWiring runtimeWiring);
Builder runtimeWiring(Consumer<RuntimeWiring.Builder> configurer);
/**
* Add {@link DataFetcherExceptionResolver}'s to use for resolving exceptions from

View File

@@ -50,13 +50,9 @@ public abstract class GraphQlTestUtils {
public static GraphQlSource.Builder initGraphQlSource(String schemaContent, String typeName, String fieldName,
DataFetcher<?> fetcher) {
RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
.type(typeName, (builder) -> builder.dataFetcher(fieldName, fetcher))
.build();
return GraphQlSource.builder()
.schemaResources(new ByteArrayResource(schemaContent.getBytes(StandardCharsets.UTF_8)))
.runtimeWiring(wiring);
.runtimeWiring(wiring -> wiring.type(typeName, (builder) -> builder.dataFetcher(fieldName, fetcher)));
}
}

View File

@@ -282,7 +282,7 @@ class QuerydslDataFetcherTests {
if (configurer != null) {
TypeRuntimeWiring.Builder typeBuilder = TypeRuntimeWiring.newTypeWiring("Query");
configurer.accept(typeBuilder);
graphQlSourceBuilder.runtimeWiring(RuntimeWiring.newRuntimeWiring().type(typeBuilder).build());
graphQlSourceBuilder.runtimeWiring(wiring -> wiring.type(typeBuilder));
}
GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor(

View File

@@ -70,20 +70,18 @@ public abstract class BookTestUtils {
}
private static GraphQlSource graphQlSource() {
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", (env) -> {
Long id = Long.parseLong(env.getArgument("id"));
return booksMap.get(id);
}));
builder.type(TypeRuntimeWiring.newTypeWiring("Subscription")
.dataFetcher("bookSearch", (env) -> {
String author = env.getArgument("author");
return Flux.fromIterable(booksMap.values()).filter((book) -> book.getAuthor().contains(author));
}));
return GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.runtimeWiring(builder.build())
.runtimeWiring(builder -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", (env) -> {
Long id = Long.parseLong(env.getArgument("id"));
return booksMap.get(id);
}))
.type(TypeRuntimeWiring.newTypeWiring("Subscription")
.dataFetcher("bookSearch", (env) -> {
String author = env.getArgument("author");
return Flux.fromIterable(booksMap.values()).filter((book) -> book.getAuthor().contains(author));
})))
.build();
}