diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index 7e1dc362..4ec3352f 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -715,12 +715,16 @@ By default, the `DataLoader` name is based on the class name of the target entit This allows an `@SchemaMapping` method to declare a <> with a generic type, and without the need for specifying a name. The name, however, can be customized through the -`BatchLoaderRegistry` builder, if necessary, along with other `DataLoader` options. +`BatchLoaderRegistry` builder, if necessary, along with other `DataLoaderOptions`. + +To configure default `DataLoaderOptions` globally, to use as a starting point for any +registration, you can override Boot's `BatchLoaderRegistry` bean and use the constructor +for `DefaultBatchLoaderRegistry` that accepts `Supplier`. For many cases, when loading related entities, you can use <> controller methods, which are a shortcut for and replace the need to use `BatchLoaderRegistry` and `DataLoader` directly. -s + `BatchLoaderRegistry` provides other important benefits too. It supports access to the same `GraphQLContext` from batch loading functions and from `@BatchMapping` methods, as well as ensures <> to them. This is why applications are expected diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java index ad20e6da..7f3bd902 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Supplier; import graphql.GraphQLContext; import io.micrometer.context.ContextSnapshot; @@ -55,6 +56,25 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { private final List> mappedLoaders = new ArrayList<>(); + private final Supplier defaultOptionsSupplier; + + + /** + * Default constructor + */ + public DefaultBatchLoaderRegistry() { + this(DataLoaderOptions::newOptions); + } + + /** + * Constructor with a default {@link DataLoaderOptions} supplier to use as + * a starting point for all registrations. + * @since 1.1 + */ + public DefaultBatchLoaderRegistry(Supplier defaultOptionsSupplier) { + this.defaultOptionsSupplier = defaultOptionsSupplier; + } + @Override public RegistrationSpec forTypePair(Class keyType, Class valueType) { @@ -69,14 +89,16 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { @Override public void registerDataLoaders(DataLoaderRegistry registry, GraphQLContext context) { BatchLoaderContextProvider contextProvider = () -> context; - DataLoaderOptions defaultOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider); + DataLoaderOptions defaultOptions = this.defaultOptionsSupplier.get(); for (ReactorBatchLoader loader : this.loaders) { - DataLoaderOptions options = loader.getOptionsOrDefault(contextProvider, defaultOptions); + DataLoaderOptions options = loader.getOptions(); + options = (options != null ? options : defaultOptions).setBatchLoaderContextProvider(contextProvider); DataLoader dataLoader = DataLoaderFactory.newDataLoader(loader, options); registerDataLoader(loader.getName(), dataLoader, registry); } for (ReactorMappedBatchLoader loader : this.mappedLoaders) { - DataLoaderOptions options = loader.getOptionsOrDefault(contextProvider, defaultOptions); + DataLoaderOptions options = loader.getOptions(); + options = (options != null ? options : defaultOptions).setBatchLoaderContextProvider(contextProvider); DataLoader dataLoader = DataLoaderFactory.newMappedDataLoader(loader, options); registerDataLoader(loader.getName(), dataLoader, registry); } @@ -101,6 +123,9 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { @Nullable private DataLoaderOptions options; + @Nullable + private Consumer optionsConsumer; + public DefaultRegistrationSpec(Class valueType) { this.valueType = valueType; } @@ -118,8 +143,8 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { @Override public RegistrationSpec withOptions(Consumer optionsConsumer) { - this.options = (this.options != null ? this.options : DataLoaderOptions.newOptions()); - optionsConsumer.accept(this.options); + this.optionsConsumer = (this.optionsConsumer != null ? + this.optionsConsumer.andThen(optionsConsumer) : optionsConsumer); return this; } @@ -132,13 +157,33 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { @Override public void registerBatchLoader(BiFunction, BatchLoaderEnvironment, Flux> loader) { DefaultBatchLoaderRegistry.this.loaders.add( - new ReactorBatchLoader<>(initName(), loader, this.options)); + new ReactorBatchLoader<>(initName(), loader, initOptionsSupplier())); } @Override public void registerMappedBatchLoader(BiFunction, BatchLoaderEnvironment, Mono>> loader) { DefaultBatchLoaderRegistry.this.mappedLoaders.add( - new ReactorMappedBatchLoader<>(initName(), loader, this.options)); + new ReactorMappedBatchLoader<>(initName(), loader, initOptionsSupplier())); + } + + @Nullable + private Supplier initOptionsSupplier() { + if (this.options == null && this.optionsConsumer == null) { + return null; + } + + Supplier optionsSupplier = + (this.options != null ? () -> this.options : defaultOptionsSupplier); + + if (this.optionsConsumer == null) { + return optionsSupplier; + } + + return () -> { + DataLoaderOptions options = optionsSupplier.get(); + this.optionsConsumer.accept(options); + return options; + }; } private String initName() { @@ -162,29 +207,24 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { private final BiFunction, BatchLoaderEnvironment, Flux> loader; @Nullable - private final DataLoaderOptions options; + private final Supplier optionsSupplier; private ReactorBatchLoader(String name, BiFunction, BatchLoaderEnvironment, Flux> loader, - @Nullable DataLoaderOptions options) { + @Nullable Supplier optionsSupplier) { this.name = name; this.loader = loader; - this.options = options; + this.optionsSupplier = optionsSupplier; } public String getName() { return this.name; } - public DataLoaderOptions getOptionsOrDefault( - BatchLoaderContextProvider provider, DataLoaderOptions defaultOptions) { - - if (this.options != null) { - return new DataLoaderOptions(this.options).setBatchLoaderContextProvider(provider); - } - - return defaultOptions; + @Nullable + public DataLoaderOptions getOptions() { + return (this.optionsSupplier != null ? this.optionsSupplier.get() : null); } @Override @@ -217,29 +257,24 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { private final BiFunction, BatchLoaderEnvironment, Mono>> loader; @Nullable - private final DataLoaderOptions options; + private final Supplier optionsSupplier; private ReactorMappedBatchLoader(String name, BiFunction, BatchLoaderEnvironment, Mono>> loader, - @Nullable DataLoaderOptions options) { + @Nullable Supplier optionsSupplier) { this.name = name; this.loader = loader; - this.options = options; + this.optionsSupplier = optionsSupplier; } public String getName() { return this.name; } - public DataLoaderOptions getOptionsOrDefault( - BatchLoaderContextProvider provider, DataLoaderOptions defaultOptions) { - - if (this.options != null) { - return new DataLoaderOptions(this.options).setBatchLoaderContextProvider(provider); - } - - return defaultOptions; + @Nullable + public DataLoaderOptions getOptions() { + return (this.optionsSupplier != null ? this.optionsSupplier.get() : null); } @Override diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistryTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistryTests.java index 26f5196e..2bdf379a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistryTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistryTests.java @@ -22,6 +22,7 @@ import java.util.function.Function; import graphql.ExecutionInput; import graphql.GraphQLContext; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderOptions; import org.dataloader.DataLoaderRegistry; import org.dataloader.stats.NoOpStatisticsCollector; import org.dataloader.stats.StatisticsCollector; @@ -40,7 +41,11 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; */ public class DefaultBatchLoaderRegistryTests { - private final BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry(); + private final BatchLoaderRegistry batchLoaderRegistry = + new DefaultBatchLoaderRegistry(() -> { + // Disable batching, so we can test loading immediately + return DataLoaderOptions.newOptions().setBatchingEnabled(false); + }); private final DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build(); @@ -50,7 +55,6 @@ public class DefaultBatchLoaderRegistryTests { AtomicReference valueRef = new AtomicReference<>(); this.batchLoaderRegistry.forTypePair(Long.class, Book.class) - .withOptions(options -> options.setBatchingEnabled(false)) // DataLoader invoked immediately .registerBatchLoader((ids, environment) -> Flux.deferContextual(contextView -> { valueRef.set(contextView.get("key")); @@ -76,7 +80,6 @@ public class DefaultBatchLoaderRegistryTests { AtomicReference valueRef = new AtomicReference<>(); this.batchLoaderRegistry.forTypePair(Long.class, Book.class) - .withOptions(options -> options.setBatchingEnabled(false)) // DataLoader invoked immediately .registerMappedBatchLoader((ids, environment) -> Mono.deferContextual(contextView -> { valueRef.set(contextView.get("key"));