-
Notifications
You must be signed in to change notification settings - Fork 94
Value cache support #88
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
Changes from 3 commits
50e4179
d651b94
ccbf15a
161adec
0f3dbef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package org.dataloader; | ||
|
||
import org.dataloader.annotations.PublicSpi; | ||
import org.dataloader.impl.NoOpCachedValueStore; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
|
||
/** | ||
* Cache value store for data loaders that use caching and want a long-lived or external cache. | ||
* <p> | ||
* The default implementation is a no-op store which replies with the key always missing and doesn't | ||
* store any actual results. This is to avoid duplicating the stored data between the {@link CacheMap} | ||
* and the store. | ||
* <p> | ||
* The API signature uses completable futures because the backing implementation MAY be a remote external cache | ||
* and hence exceptions may happen in retrieving values. | ||
* | ||
* @param <K> the type of cache keys | ||
* @param <V> the type of cache values | ||
* | ||
* @author <a href="https://github.com/craig-day">Craig Day</a> | ||
*/ | ||
@PublicSpi | ||
public interface CachedValueStore<K, V> { | ||
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 must say I am not really convinced of the name, but I also don't anything better out of my head :-) 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. What about secondLevelCache? |
||
|
||
|
||
/** | ||
* Creates a new store, using the default no-op implementation. | ||
* | ||
* @param <K> the type of cache keys | ||
* @param <V> the type of cache values | ||
* | ||
* @return the cache store | ||
*/ | ||
static <K, V> CachedValueStore<K, V> defaultCachedValueStore() { | ||
//noinspection unchecked | ||
return (CachedValueStore<K, V>) NoOpCachedValueStore.NOOP; | ||
} | ||
|
||
/** | ||
* Gets the specified key from the store. if the key si not present, then the implementation MUST return an exceptionally completed future | ||
* and not null because null is a valid cacheable value. Any exception is will cause {@link DataLoader} to load the key via batch loading | ||
* instead. | ||
* | ||
* @param key the key to retrieve | ||
* | ||
* @return a future containing the cached value (which maybe null) or exceptionally completed future if the key does | ||
* not exist in the cache. | ||
* | ||
*/ | ||
CompletableFuture<V> get(K key); | ||
|
||
/** | ||
* Stores the value with the specified key, or updates it if the key already exists. | ||
* | ||
* @param key the key to store | ||
* @param value the value to store | ||
* | ||
* @return a future containing the stored value for fluent composition | ||
*/ | ||
CompletableFuture<V> set(K key, V value); | ||
|
||
/** | ||
* Deletes the entry with the specified key from the store, if it exists. | ||
* | ||
* @param key the key to delete | ||
* | ||
* @return a void future for error handling and fluent composition | ||
*/ | ||
CompletableFuture<Void> delete(K key); | ||
|
||
/** | ||
* Clears all entries from the store. | ||
* | ||
* @return a void future for error handling and fluent composition | ||
*/ | ||
CompletableFuture<Void> clear(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.BiConsumer; | ||
|
||
import static org.dataloader.impl.Assertions.nonNull; | ||
|
||
|
@@ -64,8 +65,9 @@ | |
public class DataLoader<K, V> { | ||
|
||
private final DataLoaderHelper<K, V> helper; | ||
private final CacheMap<Object, CompletableFuture<V>> futureCache; | ||
private final StatisticsCollector stats; | ||
private final CacheMap<Object, V> futureCache; | ||
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. so maybe 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. Yes but that would definitely break everyone using it. I want that better name but I am willing to wear the non breaking API change |
||
private final CachedValueStore<Object, V> cachedValueStore; | ||
|
||
/** | ||
* Creates new DataLoader with the specified batch loader function and default options | ||
|
@@ -413,19 +415,24 @@ public DataLoader(BatchLoader<K, V> batchLoadFunction, DataLoaderOptions options | |
@VisibleForTesting | ||
DataLoader(Object batchLoadFunction, DataLoaderOptions options, Clock clock) { | ||
DataLoaderOptions loaderOptions = options == null ? new DataLoaderOptions() : options; | ||
this.futureCache = determineCacheMap(loaderOptions); | ||
this.futureCache = determineFutureCache(loaderOptions); | ||
this.cachedValueStore = determineCachedValueStore(loaderOptions); | ||
// order of keys matter in data loader | ||
this.stats = nonNull(loaderOptions.getStatisticsCollector()); | ||
|
||
this.helper = new DataLoaderHelper<>(this, batchLoadFunction, loaderOptions, this.futureCache, this.stats, clock); | ||
this.helper = new DataLoaderHelper<>(this, batchLoadFunction, loaderOptions, this.futureCache, this.cachedValueStore, this.stats, clock); | ||
} | ||
|
||
|
||
@SuppressWarnings("unchecked") | ||
private CacheMap<Object, CompletableFuture<V>> determineCacheMap(DataLoaderOptions loaderOptions) { | ||
return loaderOptions.cacheMap().isPresent() ? (CacheMap<Object, CompletableFuture<V>>) loaderOptions.cacheMap().get() : CacheMap.simpleMap(); | ||
private CacheMap<Object, V> determineFutureCache(DataLoaderOptions loaderOptions) { | ||
return (CacheMap<Object, V>) loaderOptions.cacheMap().orElseGet(CacheMap::simpleMap); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private CachedValueStore<Object, V> determineCachedValueStore(DataLoaderOptions loaderOptions) { | ||
return (CachedValueStore<Object, V>) loaderOptions.cachedValueStore().orElseGet(CachedValueStore::defaultCachedValueStore); | ||
} | ||
|
||
/** | ||
* This returns the last instant the data loader was dispatched. When the data loader is created this value is set to now. | ||
|
@@ -628,9 +635,24 @@ public int dispatchDepth() { | |
* @return the data loader for fluent coding | ||
*/ | ||
public DataLoader<K, V> clear(K key) { | ||
return clear(key, (v, e) -> { | ||
}); | ||
} | ||
|
||
/** | ||
* Clears the future with the specified key from the cache remote value store, if caching is enabled | ||
* and a remote store is set, so it will be re-fetched and stored on the next load request. | ||
* | ||
* @param key the key to remove | ||
* @param handler a handler that will be called after the async remote clear completes | ||
* | ||
* @return the data loader for fluent coding | ||
*/ | ||
public DataLoader<K, V> clear(K key, BiConsumer<Void, Throwable> handler) { | ||
Object cacheKey = getCacheKey(key); | ||
synchronized (this) { | ||
futureCache.delete(cacheKey); | ||
cachedValueStore.delete(key).whenComplete(handler); | ||
} | ||
return this; | ||
} | ||
|
@@ -641,14 +663,29 @@ public DataLoader<K, V> clear(K key) { | |
* @return the data loader for fluent coding | ||
*/ | ||
public DataLoader<K, V> clearAll() { | ||
return clearAll((v, e) -> { | ||
}); | ||
} | ||
|
||
/** | ||
* Clears the entire cache map of the loader, and of the cached value store. | ||
* | ||
* @param handler a handler that will be called after the async remote clear all completes | ||
* | ||
* @return the data loader for fluent coding | ||
*/ | ||
public DataLoader<K, V> clearAll(BiConsumer<Void, Throwable> handler) { | ||
synchronized (this) { | ||
futureCache.clear(); | ||
cachedValueStore.clear().whenComplete(handler); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* Primes the cache with the given key and value. | ||
* Primes the cache with the given key and value. Note this will only prime the future cache | ||
* and not the value store. Use {@link CachedValueStore#set(Object, Object)} if you want | ||
* o prime it with values before use | ||
* | ||
* @param key the key | ||
* @param value the value | ||
|
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.
The small brekaing change.
It was always a CompleteableFuture at runtime but now the signature reflects this more correctly