package com.linkedin.parseq.batching;
import java.util.Map;
import java.util.Set;
import com.linkedin.parseq.Context;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.function.Try;
/**
* This is a base class for a batching strategy that leverages existing Task-based API.
* <p>
* Example below shows how to build a ParSeq client for a key-value store that provides
* transparent batching given existing Task-based API. Let's assume that we have an implementation
* of the following key-value store interface:
* <blockquote><pre>
* interface KVStore {
* Task{@code <String>} get(Long key);
* Task{@code <Map<Long, Try<String>>>} batchGet(Collection{@code <Long>} keys);
* }
* </pre></blockquote>
*
* We can then implement a {@code TaskBatchingStrategy} in the following way (for the sake
* of simplicity we assume that all keys can be grouped into one batch thus we implement
* {@code SimpleTaskBatchingStrategy}):
* <blockquote><pre>
* public static class BatchingKVStoreClient extends SimpleTaskBatchingStrategy{@code <Long, String>} {
* private final KVStore _store;
* public BatchingKVStoreClient(KVStore store) {
* _store = store;
* }
*
* {@code @Override}
* public void executeBatch(Integer group, Batch{@code <Long, String>} batch) {
* Map{@code <Long, String>} batchResult = _store.batchGet(batch.keys());
* batch.foreach((key, promise) {@code ->} promise.done(batchResult.get(key)));
* }
*
* {@code @Override}
* public {@code Task<Map<Long, Try<String>>>} taskForBatch(Set{@code <Long>} keys) {
* return _store.batchGet(keys);
* }
* }
* </pre></blockquote>
*
* {@code taskForBatch} method returns a task that computes a map that for every key contains
* either a success with a value or a failure. If returned map does not contain results for
* some keys the tasks for which results are missing will fail.
*
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*
* @param <G> Type of a Group
* @param <K> Type of a Key
* @param <T> Type of a Value
*
* @see BatchingStrategy
* @see SimpleTaskBatchingStrategy
*/
public abstract class TaskBatchingStrategy<G, K, T> extends BatchingStrategy<G, K, T> {
@Override
public final void executeBatch(G group, Batch<K, T> batch) {
// This method should be unreachable because we also override executeBatchWithContext
throw new IllegalStateException("This method should be unreachable");
}
@Override
protected void executeBatchWithContext(final G group, final Batch<K, T> batch, final Context ctx) {
Task<Map<K, Try<T>>> task = taskForBatch(group, batch.keys());
Task<Map<K, Try<T>>> completing = task.andThen("completePromises", map -> {
batch.foreach((key, promise) -> {
Try<T> result = map.get(key);
if (result != null) {
if (result.isFailed()) {
promise.fail(result.getError());
} else {
promise.done(result.get());
}
} else {
promise.fail(new Exception("Result for key: " + key + " not found in batch response"));
}
});
});
completing.getShallowTraceBuilder().setSystemHidden(true);
Task<Map<K, Try<T>>> withFailureHandling = completing.onFailure("handleFailures", t -> {
batch.failAll(t);
});
withFailureHandling.getShallowTraceBuilder().setSystemHidden(true);
ctx.run(withFailureHandling);
}
@Override
final public String getBatchName(G group, Batch<K, T> batch) {
return getBatchName(group, batch.keys());
}
/**
* Overriding this method allows providing custom name for a batch. Name will appear in the
* ParSeq trace as a description of the task that executes the batch.
* @param keys set of keys belonging to the batch that needs to be described
* @param group group to be described
* @return name for the batch
*/
public String getBatchName(G group, Set<K> keys) {
return "batch(" + keys.size() + ")";
}
/**
* This method will be called for every batch. It returns a map that for every key contains
* either a success with a value or a failure. If returned map does not contain results for
* some keys the tasks for which results are missing will fail.
* @param group group that represents the batch
* @param keys set of keys belonging to the batch
* @return A map that for every key contains either a success with a value or a failure.
* If returned map does not contain results for some keys the tasks for which results are missing will fail.
*/
public abstract Task<Map<K, Try<T>>> taskForBatch(G group, Set<K> keys);
}