-
Notifications
You must be signed in to change notification settings - Fork 94
Breaking change - adds a name to a DataLoader #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
02a485d
bf3edd2
496a298
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
import org.dataloader.instrumentation.DataLoaderInstrumentation; | ||
import org.dataloader.instrumentation.DataLoaderInstrumentationHelper; | ||
import org.dataloader.stats.Statistics; | ||
import org.jspecify.annotations.NullMarked; | ||
import org.jspecify.annotations.Nullable; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
|
@@ -16,6 +18,8 @@ | |
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Function; | ||
|
||
import static org.dataloader.impl.Assertions.assertState; | ||
|
||
/** | ||
* This allows data loaders to be registered together into a single place, so | ||
* they can be dispatched as one. It also allows you to retrieve data loaders by | ||
|
@@ -35,9 +39,10 @@ | |
* are the same object, then nothing is changed, since the same instrumentation code is being run. | ||
*/ | ||
@PublicApi | ||
@NullMarked | ||
public class DataLoaderRegistry { | ||
protected final Map<String, DataLoader<?, ?>> dataLoaders; | ||
protected final DataLoaderInstrumentation instrumentation; | ||
protected final @Nullable DataLoaderInstrumentation instrumentation; | ||
|
||
|
||
public DataLoaderRegistry() { | ||
|
@@ -48,27 +53,30 @@ private DataLoaderRegistry(Builder builder) { | |
this(builder.dataLoaders, builder.instrumentation); | ||
} | ||
|
||
protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, DataLoaderInstrumentation instrumentation) { | ||
protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, @Nullable DataLoaderInstrumentation instrumentation) { | ||
this.dataLoaders = instrumentDLs(dataLoaders, instrumentation); | ||
this.instrumentation = instrumentation; | ||
} | ||
|
||
private Map<String, DataLoader<?, ?>> instrumentDLs(Map<String, DataLoader<?, ?>> incomingDataLoaders, DataLoaderInstrumentation registryInstrumentation) { | ||
private Map<String, DataLoader<?, ?>> instrumentDLs(Map<String, DataLoader<?, ?>> incomingDataLoaders, @Nullable DataLoaderInstrumentation registryInstrumentation) { | ||
Map<String, DataLoader<?, ?>> dataLoaders = new ConcurrentHashMap<>(incomingDataLoaders); | ||
if (registryInstrumentation != null) { | ||
dataLoaders.replaceAll((k, existingDL) -> instrumentDL(registryInstrumentation, existingDL)); | ||
dataLoaders.replaceAll((k, existingDL) -> nameAndInstrumentDL(k, registryInstrumentation, existingDL)); | ||
} | ||
return dataLoaders; | ||
} | ||
|
||
/** | ||
* Can be called to tweak a {@link DataLoader} so that it has the registry {@link DataLoaderInstrumentation} added as the first one. | ||
* | ||
* @param key the key used to register the data loader | ||
* @param registryInstrumentation the common registry {@link DataLoaderInstrumentation} | ||
* @param existingDL the existing data loader | ||
* @return a new {@link DataLoader} or the same one if there is nothing to change | ||
*/ | ||
private static DataLoader<?, ?> instrumentDL(DataLoaderInstrumentation registryInstrumentation, DataLoader<?, ?> existingDL) { | ||
private static DataLoader<?, ?> nameAndInstrumentDL(String key, @Nullable DataLoaderInstrumentation registryInstrumentation, DataLoader<?, ?> existingDL) { | ||
existingDL = checkAndSetName(key, existingDL); | ||
|
||
if (registryInstrumentation == null) { | ||
return existingDL; | ||
} | ||
|
@@ -97,6 +105,15 @@ protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, DataLoad | |
} | ||
} | ||
|
||
private static DataLoader<?, ?> checkAndSetName(String key, DataLoader<?, ?> dataLoader) { | ||
if (dataLoader.getName() == null) { | ||
return dataLoader.transform(b -> b.name(key)); | ||
} | ||
assertState(key.equals(dataLoader.getName()), | ||
() -> String.format("Data loader name '%s' is not the same as registered key '%s'", dataLoader.getName(), key)); | ||
return dataLoader; | ||
} | ||
|
||
private static DataLoader<?, ?> mkInstrumentedDataLoader(DataLoader<?, ?> existingDL, DataLoaderOptions options, DataLoaderInstrumentation newInstrumentation) { | ||
return existingDL.transform(builder -> builder.options(setInInstrumentation(options, newInstrumentation))); | ||
} | ||
|
@@ -108,28 +125,70 @@ private static DataLoaderOptions setInInstrumentation(DataLoaderOptions options, | |
/** | ||
* @return the {@link DataLoaderInstrumentation} associated with this registry which can be null | ||
*/ | ||
public DataLoaderInstrumentation getInstrumentation() { | ||
public @Nullable DataLoaderInstrumentation getInstrumentation() { | ||
return instrumentation; | ||
} | ||
|
||
/** | ||
* This will register a new dataloader | ||
* This will register a new named dataloader. The {@link DataLoader} must be named something and | ||
* cannot have a null name. | ||
* <p> | ||
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to | ||
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same | ||
* object that was registered. | ||
* | ||
* @param dataLoader the named data loader to register | ||
* @return this registry | ||
*/ | ||
public DataLoaderRegistry register(DataLoader<?, ?> dataLoader) { | ||
String name = dataLoader.getName(); | ||
assertState(name != null, () -> "The DataLoader must have a non null name"); | ||
dataLoaders.put(name, nameAndInstrumentDL(name, instrumentation, dataLoader)); | ||
return this; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New - we can register "named" DL now |
||
|
||
/** | ||
* This will register a new {@link DataLoader} | ||
* <p> | ||
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to | ||
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same | ||
* object that was registered. | ||
* | ||
* @param key the key to put the data loader under | ||
* @param dataLoader the data loader to register | ||
* @return this registry | ||
*/ | ||
public DataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since i can give a name to a dataloader and register it with a different name, or register the same DL multiple times with a different name (different options attached), what about providing a pass through cache key into the instrumentation i.e. DataLoader loader, String dataLoaderKey in the instrumentation rather than attaching an explicit name to the loader itself. This way register is the SoT. I feel if we allow both we might introduce some slight oddities of dataLoaderKey != dataLoaderName else you fail at runtime with Don't allow creation with a name, only allow association at registry time and pass through to instrumentation since keys can be only a registry thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gave some thought to this but there is no real link between a So the instrumentation for a DL can be run without it coming from a DLR So while you can register them "wrong" - so be it - it validates |
||
dataLoaders.put(key, instrumentDL(instrumentation, dataLoader)); | ||
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader)); | ||
return this; | ||
} | ||
|
||
/** | ||
* This will register a new {@link DataLoader} and then return it. | ||
* <p> | ||
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to | ||
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same | ||
* object that was registered. | ||
* | ||
* @param key the key to put the data loader under | ||
* @param dataLoader the data loader to register | ||
* @return the data loader instance that was registered | ||
*/ | ||
public <K, V> DataLoader<K, V> registerAndGet(String key, DataLoader<?, ?> dataLoader) { | ||
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader)); | ||
return getDataLoader(key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New - gives back the DL immediately - it may have changed |
||
} | ||
|
||
/** | ||
* Computes a data loader if absent or return it if it was | ||
* already registered at that key. | ||
* <p> | ||
* Note: The entire method invocation is performed atomically, | ||
* so the function is applied at most once per key. | ||
* <p> | ||
* Note: Registration can change the data loader instance since it might get an {@link DataLoaderInstrumentation} applied to | ||
* it. So the {@link DataLoader} instance your read via {@link DataLoaderRegistry#getDataLoader(String)} might not be the same | ||
* object that was registered. | ||
* | ||
* @param key the key of the data loader | ||
* @param mappingFunction the function to compute a data loader | ||
|
@@ -142,7 +201,7 @@ public <K, V> DataLoader<K, V> computeIfAbsent(final String key, | |
final Function<String, DataLoader<?, ?>> mappingFunction) { | ||
return (DataLoader<K, V>) dataLoaders.computeIfAbsent(key, (k) -> { | ||
DataLoader<?, ?> dl = mappingFunction.apply(k); | ||
return instrumentDL(instrumentation, dl); | ||
return nameAndInstrumentDL(key, instrumentation, dl); | ||
}); | ||
} | ||
|
||
|
@@ -262,7 +321,7 @@ public static Builder newRegistry() { | |
public static class Builder { | ||
|
||
private final Map<String, DataLoader<?, ?>> dataLoaders = new HashMap<>(); | ||
private DataLoaderInstrumentation instrumentation; | ||
private @Nullable DataLoaderInstrumentation instrumentation; | ||
|
||
/** | ||
* This will register a new dataloader | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package org.dataloader; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.stream.Collectors; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
||
class DataLoaderFactoryTest { | ||
|
||
@Test | ||
void can_create_via_builder() { | ||
BatchLoaderWithContext<String, String> loader = (keys, environment) -> CompletableFuture.completedFuture(keys); | ||
DataLoaderOptions options = DataLoaderOptions.newOptionsBuilder().setBatchingEnabled(true).build(); | ||
|
||
DataLoader<String, String> dl = DataLoaderFactory.<String, String>builder() | ||
.name("x").batchLoader(loader).options(options).build(); | ||
|
||
assertNotNull(dl.getName()); | ||
assertThat(dl.getName(), equalTo("x")); | ||
assertThat(dl.getBatchLoadFunction(), equalTo(loader)); | ||
assertThat(dl.getOptions(), equalTo(options)); | ||
|
||
BatchLoaderWithContext<String, Try<String>> loaderTry = (keys, environment) | ||
-> CompletableFuture.completedFuture(keys.stream().map(Try::succeeded).collect(Collectors.toList())); | ||
|
||
DataLoader<String, Try<String>> dlTry = DataLoaderFactory.<String, Try<String>>builder() | ||
.name("try").batchLoader(loaderTry).options(options).build(); | ||
|
||
assertNotNull(dlTry.getName()); | ||
assertThat(dlTry.getName(), equalTo("try")); | ||
assertThat(dlTry.getBatchLoadFunction(), equalTo(loaderTry)); | ||
assertThat(dlTry.getOptions(), equalTo(options)); | ||
|
||
MappedBatchLoader<String, Try<String>> mappedLoaderTry = (keys) | ||
-> CompletableFuture.completedFuture( | ||
keys.stream().collect(Collectors.toMap(k -> k, Try::succeeded)) | ||
); | ||
|
||
DataLoader<String, Try<String>> dlTry2 = DataLoaderFactory.<String, Try<String>>builder() | ||
.name("try2").mappedBatchLoader(mappedLoaderTry).options(options).build(); | ||
|
||
assertNotNull(dlTry2.getName()); | ||
assertThat(dlTry2.getName(), equalTo("try2")); | ||
assertThat(dlTry2.getBatchLoadFunction(), equalTo(mappedLoaderTry)); | ||
assertThat(dlTry2.getOptions(), equalTo(options)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It validates that a DL named "Foo" has been registered as the key "Foo" because
dlr.register("Bar", fooDL)
makes not sense