Skip to content

Commit 08b8f01

Browse files
authored
Merge pull request #193 from graphql-java/add-name-to-data-loader
Breaking change - adds a name to a DataLoader
2 parents 42eae0c + 496a298 commit 08b8f01

12 files changed

+579
-380
lines changed

src/main/java/org/dataloader/DataLoader.java

+20-308
Large diffs are not rendered by default.

src/main/java/org/dataloader/DataLoaderFactory.java

+331-21
Large diffs are not rendered by default.

src/main/java/org/dataloader/DataLoaderRegistry.java

+69-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import org.dataloader.instrumentation.DataLoaderInstrumentation;
66
import org.dataloader.instrumentation.DataLoaderInstrumentationHelper;
77
import org.dataloader.stats.Statistics;
8+
import org.jspecify.annotations.NullMarked;
9+
import org.jspecify.annotations.Nullable;
810

911
import java.util.ArrayList;
1012
import java.util.HashMap;
@@ -16,6 +18,8 @@
1618
import java.util.concurrent.ConcurrentHashMap;
1719
import java.util.function.Function;
1820

21+
import static org.dataloader.impl.Assertions.assertState;
22+
1923
/**
2024
* This allows data loaders to be registered together into a single place, so
2125
* they can be dispatched as one. It also allows you to retrieve data loaders by
@@ -35,9 +39,10 @@
3539
* are the same object, then nothing is changed, since the same instrumentation code is being run.
3640
*/
3741
@PublicApi
42+
@NullMarked
3843
public class DataLoaderRegistry {
3944
protected final Map<String, DataLoader<?, ?>> dataLoaders;
40-
protected final DataLoaderInstrumentation instrumentation;
45+
protected final @Nullable DataLoaderInstrumentation instrumentation;
4146

4247

4348
public DataLoaderRegistry() {
@@ -48,27 +53,30 @@ private DataLoaderRegistry(Builder builder) {
4853
this(builder.dataLoaders, builder.instrumentation);
4954
}
5055

51-
protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, DataLoaderInstrumentation instrumentation) {
56+
protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, @Nullable DataLoaderInstrumentation instrumentation) {
5257
this.dataLoaders = instrumentDLs(dataLoaders, instrumentation);
5358
this.instrumentation = instrumentation;
5459
}
5560

56-
private Map<String, DataLoader<?, ?>> instrumentDLs(Map<String, DataLoader<?, ?>> incomingDataLoaders, DataLoaderInstrumentation registryInstrumentation) {
61+
private Map<String, DataLoader<?, ?>> instrumentDLs(Map<String, DataLoader<?, ?>> incomingDataLoaders, @Nullable DataLoaderInstrumentation registryInstrumentation) {
5762
Map<String, DataLoader<?, ?>> dataLoaders = new ConcurrentHashMap<>(incomingDataLoaders);
5863
if (registryInstrumentation != null) {
59-
dataLoaders.replaceAll((k, existingDL) -> instrumentDL(registryInstrumentation, existingDL));
64+
dataLoaders.replaceAll((k, existingDL) -> nameAndInstrumentDL(k, registryInstrumentation, existingDL));
6065
}
6166
return dataLoaders;
6267
}
6368

6469
/**
6570
* Can be called to tweak a {@link DataLoader} so that it has the registry {@link DataLoaderInstrumentation} added as the first one.
6671
*
72+
* @param key the key used to register the data loader
6773
* @param registryInstrumentation the common registry {@link DataLoaderInstrumentation}
6874
* @param existingDL the existing data loader
6975
* @return a new {@link DataLoader} or the same one if there is nothing to change
7076
*/
71-
private static DataLoader<?, ?> instrumentDL(DataLoaderInstrumentation registryInstrumentation, DataLoader<?, ?> existingDL) {
77+
private static DataLoader<?, ?> nameAndInstrumentDL(String key, @Nullable DataLoaderInstrumentation registryInstrumentation, DataLoader<?, ?> existingDL) {
78+
existingDL = checkAndSetName(key, existingDL);
79+
7280
if (registryInstrumentation == null) {
7381
return existingDL;
7482
}
@@ -97,6 +105,15 @@ protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, DataLoad
97105
}
98106
}
99107

108+
private static DataLoader<?, ?> checkAndSetName(String key, DataLoader<?, ?> dataLoader) {
109+
if (dataLoader.getName() == null) {
110+
return dataLoader.transform(b -> b.name(key));
111+
}
112+
assertState(key.equals(dataLoader.getName()),
113+
() -> String.format("Data loader name '%s' is not the same as registered key '%s'", dataLoader.getName(), key));
114+
return dataLoader;
115+
}
116+
100117
private static DataLoader<?, ?> mkInstrumentedDataLoader(DataLoader<?, ?> existingDL, DataLoaderOptions options, DataLoaderInstrumentation newInstrumentation) {
101118
return existingDL.transform(builder -> builder.options(setInInstrumentation(options, newInstrumentation)));
102119
}
@@ -108,28 +125,70 @@ private static DataLoaderOptions setInInstrumentation(DataLoaderOptions options,
108125
/**
109126
* @return the {@link DataLoaderInstrumentation} associated with this registry which can be null
110127
*/
111-
public DataLoaderInstrumentation getInstrumentation() {
128+
public @Nullable DataLoaderInstrumentation getInstrumentation() {
112129
return instrumentation;
113130
}
114131

115132
/**
116-
* This will register a new dataloader
133+
* This will register a new named dataloader. The {@link DataLoader} must be named something and
134+
* cannot have a null name.
135+
* <p>
136+
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to
137+
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same
138+
* object that was registered.
139+
*
140+
* @param dataLoader the named data loader to register
141+
* @return this registry
142+
*/
143+
public DataLoaderRegistry register(DataLoader<?, ?> dataLoader) {
144+
String name = dataLoader.getName();
145+
assertState(name != null, () -> "The DataLoader must have a non null name");
146+
dataLoaders.put(name, nameAndInstrumentDL(name, instrumentation, dataLoader));
147+
return this;
148+
}
149+
150+
/**
151+
* This will register a new {@link DataLoader}
152+
* <p>
153+
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to
154+
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same
155+
* object that was registered.
117156
*
118157
* @param key the key to put the data loader under
119158
* @param dataLoader the data loader to register
120159
* @return this registry
121160
*/
122161
public DataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader) {
123-
dataLoaders.put(key, instrumentDL(instrumentation, dataLoader));
162+
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader));
124163
return this;
125164
}
126165

166+
/**
167+
* This will register a new {@link DataLoader} and then return it.
168+
* <p>
169+
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to
170+
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same
171+
* object that was registered.
172+
*
173+
* @param key the key to put the data loader under
174+
* @param dataLoader the data loader to register
175+
* @return the data loader instance that was registered
176+
*/
177+
public <K, V> DataLoader<K, V> registerAndGet(String key, DataLoader<?, ?> dataLoader) {
178+
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader));
179+
return getDataLoader(key);
180+
}
181+
127182
/**
128183
* Computes a data loader if absent or return it if it was
129184
* already registered at that key.
130185
* <p>
131186
* Note: The entire method invocation is performed atomically,
132187
* so the function is applied at most once per key.
188+
* <p>
189+
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to
190+
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same
191+
* object that was registered.
133192
*
134193
* @param key the key of the data loader
135194
* @param mappingFunction the function to compute a data loader
@@ -142,7 +201,7 @@ public <K, V> DataLoader<K, V> computeIfAbsent(final String key,
142201
final Function<String, DataLoader<?, ?>> mappingFunction) {
143202
return (DataLoader<K, V>) dataLoaders.computeIfAbsent(key, (k) -> {
144203
DataLoader<?, ?> dl = mappingFunction.apply(k);
145-
return instrumentDL(instrumentation, dl);
204+
return nameAndInstrumentDL(key, instrumentation, dl);
146205
});
147206
}
148207

@@ -262,7 +321,7 @@ public static Builder newRegistry() {
262321
public static class Builder {
263322

264323
private final Map<String, DataLoader<?, ?>> dataLoaders = new HashMap<>();
265-
private DataLoaderInstrumentation instrumentation;
324+
private @Nullable DataLoaderInstrumentation instrumentation;
266325

267326
/**
268327
* This will register a new dataloader

src/main/java/org/dataloader/DelegatingDataLoader.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
* CompletableFuture<String> cf = super.load(key, keyContext);
3030
* return cf.thenApply(v -> "|" + v + "|");
3131
* }
32-
*};
33-
*}</pre>
32+
* };
33+
* }</pre>
3434
*
3535
* @param <K> type parameter indicating the type of the data load keys
3636
* @param <V> type parameter indicating the type of the data that is returned
@@ -58,7 +58,7 @@ public static <K, V> DataLoader<K, V> unwrap(DataLoader<K, V> dataLoader) {
5858
}
5959

6060
public DelegatingDataLoader(DataLoader<K, V> delegate) {
61-
super(delegate.getBatchLoadFunction(), delegate.getOptions());
61+
super(delegate.getName(), delegate.getBatchLoadFunction(), delegate.getOptions());
6262
this.delegate = delegate;
6363
}
6464

src/main/java/org/dataloader/registries/ScheduledDataLoaderRegistry.java

+7-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import org.dataloader.DataLoader;
44
import org.dataloader.DataLoaderRegistry;
55
import org.dataloader.annotations.ExperimentalApi;
6+
import org.dataloader.impl.Assertions;
67
import org.dataloader.instrumentation.DataLoaderInstrumentation;
8+
import org.jspecify.annotations.NullMarked;
9+
import org.jspecify.annotations.Nullable;
710

811
import java.time.Duration;
912
import java.util.LinkedHashMap;
@@ -54,6 +57,7 @@
5457
* This code is currently marked as {@link ExperimentalApi}
5558
*/
5659
@ExperimentalApi
60+
@NullMarked
5761
public class ScheduledDataLoaderRegistry extends DataLoaderRegistry implements AutoCloseable {
5862

5963
private final Map<DataLoader<?, ?>, DispatchPredicate> dataLoaderPredicates = new ConcurrentHashMap<>();
@@ -66,7 +70,7 @@ public class ScheduledDataLoaderRegistry extends DataLoaderRegistry implements A
6670

6771
private ScheduledDataLoaderRegistry(Builder builder) {
6872
super(builder.dataLoaders, builder.instrumentation);
69-
this.scheduledExecutorService = builder.scheduledExecutorService;
73+
this.scheduledExecutorService = Assertions.nonNull(builder.scheduledExecutorService);
7074
this.defaultExecutorUsed = builder.defaultExecutorUsed;
7175
this.schedule = builder.schedule;
7276
this.tickerMode = builder.tickerMode;
@@ -112,7 +116,6 @@ public boolean isTickerMode() {
112116
* and return a new combined registry
113117
*
114118
* @param registry the registry to combine into this registry
115-
*
116119
* @return a new combined registry
117120
*/
118121
public ScheduledDataLoaderRegistry combine(DataLoaderRegistry registry) {
@@ -128,7 +131,6 @@ public ScheduledDataLoaderRegistry combine(DataLoaderRegistry registry) {
128131
* This will unregister a new dataloader
129132
*
130133
* @param key the key of the data loader to unregister
131-
*
132134
* @return this registry
133135
*/
134136
public ScheduledDataLoaderRegistry unregister(String key) {
@@ -161,7 +163,6 @@ public DispatchPredicate getDispatchPredicate() {
161163
* @param key the key to put the data loader under
162164
* @param dataLoader the data loader to register
163165
* @param dispatchPredicate the dispatch predicate to associate with this data loader
164-
*
165166
* @return this registry
166167
*/
167168
public ScheduledDataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader, DispatchPredicate dispatchPredicate) {
@@ -222,7 +223,6 @@ public void rescheduleNow() {
222223
*
223224
* @param dataLoaderKey the key in the dataloader map
224225
* @param dataLoader the dataloader
225-
*
226226
* @return true if it should dispatch
227227
*/
228228
private boolean shouldDispatch(String dataLoaderKey, DataLoader<?, ?> dataLoader) {
@@ -267,19 +267,18 @@ public static class Builder {
267267
private final Map<String, DataLoader<?, ?>> dataLoaders = new LinkedHashMap<>();
268268
private final Map<DataLoader<?, ?>, DispatchPredicate> dataLoaderPredicates = new LinkedHashMap<>();
269269
private DispatchPredicate dispatchPredicate = DispatchPredicate.DISPATCH_ALWAYS;
270-
private ScheduledExecutorService scheduledExecutorService;
270+
private @Nullable ScheduledExecutorService scheduledExecutorService;
271271
private boolean defaultExecutorUsed = false;
272272
private Duration schedule = Duration.ofMillis(10);
273273
private boolean tickerMode = false;
274-
private DataLoaderInstrumentation instrumentation;
274+
private @Nullable DataLoaderInstrumentation instrumentation;
275275

276276

277277
/**
278278
* If you provide a {@link ScheduledExecutorService} then it will NOT be shutdown when
279279
* {@link ScheduledDataLoaderRegistry#close()} is called. This is left to the code that made this setup code
280280
*
281281
* @param executorService the executor service to run the ticker on
282-
*
283282
* @return this builder for a fluent pattern
284283
*/
285284
public Builder scheduledExecutorService(ScheduledExecutorService executorService) {
@@ -297,7 +296,6 @@ public Builder schedule(Duration schedule) {
297296
*
298297
* @param key the key to put the data loader under
299298
* @param dataLoader the data loader to register
300-
*
301299
* @return this builder for a fluent pattern
302300
*/
303301
public Builder register(String key, DataLoader<?, ?> dataLoader) {
@@ -312,7 +310,6 @@ public Builder register(String key, DataLoader<?, ?> dataLoader) {
312310
* @param key the key to put the data loader under
313311
* @param dataLoader the data loader to register
314312
* @param dispatchPredicate the dispatch predicate
315-
*
316313
* @return this builder for a fluent pattern
317314
*/
318315
public Builder register(String key, DataLoader<?, ?> dataLoader, DispatchPredicate dispatchPredicate) {
@@ -326,7 +323,6 @@ public Builder register(String key, DataLoader<?, ?> dataLoader, DispatchPredica
326323
* from a previous {@link DataLoaderRegistry}
327324
*
328325
* @param otherRegistry the previous {@link DataLoaderRegistry}
329-
*
330326
* @return this builder for a fluent pattern
331327
*/
332328
public Builder registerAll(DataLoaderRegistry otherRegistry) {
@@ -343,7 +339,6 @@ public Builder registerAll(DataLoaderRegistry otherRegistry) {
343339
* whether all {@link DataLoader}s in the {@link DataLoaderRegistry }should be dispatched.
344340
*
345341
* @param dispatchPredicate the predicate
346-
*
347342
* @return this builder for a fluent pattern
348343
*/
349344
public Builder dispatchPredicate(DispatchPredicate dispatchPredicate) {
@@ -357,7 +352,6 @@ public Builder dispatchPredicate(DispatchPredicate dispatchPredicate) {
357352
* to dispatchAll.
358353
*
359354
* @param tickerMode true or false
360-
*
361355
* @return this builder for a fluent pattern
362356
*/
363357
public Builder tickerMode(boolean tickerMode) {

src/test/java/org/dataloader/ClockDataLoader.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public ClockDataLoader(Object batchLoadFunction, Clock clock) {
99
}
1010

1111
public ClockDataLoader(Object batchLoadFunction, DataLoaderOptions options, Clock clock) {
12-
super(batchLoadFunction, options, clock);
12+
super(null, batchLoadFunction, options, clock);
1313
}
1414

1515
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.dataloader;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.stream.Collectors;
7+
8+
import static org.hamcrest.MatcherAssert.assertThat;
9+
import static org.hamcrest.Matchers.equalTo;
10+
import static org.junit.jupiter.api.Assertions.assertNotNull;
11+
12+
class DataLoaderFactoryTest {
13+
14+
@Test
15+
void can_create_via_builder() {
16+
BatchLoaderWithContext<String, String> loader = (keys, environment) -> CompletableFuture.completedFuture(keys);
17+
DataLoaderOptions options = DataLoaderOptions.newOptionsBuilder().setBatchingEnabled(true).build();
18+
19+
DataLoader<String, String> dl = DataLoaderFactory.<String, String>builder()
20+
.name("x").batchLoader(loader).options(options).build();
21+
22+
assertNotNull(dl.getName());
23+
assertThat(dl.getName(), equalTo("x"));
24+
assertThat(dl.getBatchLoadFunction(), equalTo(loader));
25+
assertThat(dl.getOptions(), equalTo(options));
26+
27+
BatchLoaderWithContext<String, Try<String>> loaderTry = (keys, environment)
28+
-> CompletableFuture.completedFuture(keys.stream().map(Try::succeeded).collect(Collectors.toList()));
29+
30+
DataLoader<String, Try<String>> dlTry = DataLoaderFactory.<String, Try<String>>builder()
31+
.name("try").batchLoader(loaderTry).options(options).build();
32+
33+
assertNotNull(dlTry.getName());
34+
assertThat(dlTry.getName(), equalTo("try"));
35+
assertThat(dlTry.getBatchLoadFunction(), equalTo(loaderTry));
36+
assertThat(dlTry.getOptions(), equalTo(options));
37+
38+
MappedBatchLoader<String, Try<String>> mappedLoaderTry = (keys)
39+
-> CompletableFuture.completedFuture(
40+
keys.stream().collect(Collectors.toMap(k -> k, Try::succeeded))
41+
);
42+
43+
DataLoader<String, Try<String>> dlTry2 = DataLoaderFactory.<String, Try<String>>builder()
44+
.name("try2").mappedBatchLoader(mappedLoaderTry).options(options).build();
45+
46+
assertNotNull(dlTry2.getName());
47+
assertThat(dlTry2.getName(), equalTo("try2"));
48+
assertThat(dlTry2.getBatchLoadFunction(), equalTo(mappedLoaderTry));
49+
assertThat(dlTry2.getOptions(), equalTo(options));
50+
}
51+
}

0 commit comments

Comments
 (0)