Last active
January 13, 2024 16:56
-
-
Save mhewedy/d09cef74a614613be9709e38a2b5c5ae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import lombok.AccessLevel; | |
| import lombok.NoArgsConstructor; | |
| import lombok.extern.slf4j.Slf4j; | |
| import org.springframework.data.redis.core.RedisTemplate; | |
| import java.time.Duration; | |
| import java.util.function.Supplier; | |
| @Slf4j | |
| public class Idempotent { | |
| private static final String KEY_PREFIX = "idempotent-"; | |
| private static final Options DEFAULT_OPTIONS = new Options(); | |
| private static RedisTemplate<String, Object> redisTemplate; | |
| /** | |
| * Run with default options | |
| */ | |
| public static void run(String key, Runnable action) { | |
| run(key, DEFAULT_OPTIONS, action); | |
| } | |
| public static void run(String key, Options options, Runnable action) { | |
| run(key, options, runnableToSupplier(action)); | |
| } | |
| /** | |
| * Run with default options | |
| */ | |
| public static <T> T run(String key, Supplier<T> action) { | |
| return run(key, DEFAULT_OPTIONS, action); | |
| } | |
| public static <T> T run(String key, Options options, Supplier<T> action) { | |
| initRedisTemplate(); | |
| checkKeyExists(key, options.errorKey); | |
| if (options.lockBefore) { | |
| writeKey(key, options.duration); | |
| } | |
| try { | |
| T result = action.get(); | |
| if (!options.lockBefore) { | |
| writeKey(key, options.duration); | |
| } | |
| return result; | |
| } finally { | |
| if (!options.keepLock) { | |
| removeKey(key); | |
| } | |
| } | |
| } | |
| /** | |
| * Use {@link Idempotent#options()} to create default {@link Options} object. | |
| */ | |
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | |
| public static class Options { | |
| private Duration duration = Duration.ofMinutes(5); | |
| private boolean lockBefore = true; | |
| private boolean keepLock = false; | |
| private String errorKey = "operation_in_progress"; | |
| /** | |
| * the maximum duration to lock, default is 5 minutes | |
| */ | |
| public Options duration(Duration d) { | |
| this.duration = d; | |
| return this; | |
| } | |
| /** | |
| * whether to lock before the execution or not, default is true. | |
| */ | |
| public Options lockBefore(boolean b) { | |
| this.lockBefore = b; | |
| return this; | |
| } | |
| /** | |
| * keep the lock for the whole duration regardless of whether the execution is done or not, default is false. | |
| */ | |
| public Options keepLock(boolean b) { | |
| this.keepLock = b; | |
| return this; | |
| } | |
| /** | |
| * error key to thrown in case of operation is in progress, default is "operation_in_progress". | |
| */ | |
| public Options errorKey(String key) { | |
| this.errorKey = key; | |
| return this; | |
| } | |
| } | |
| public static Options options() { | |
| return new Options(); | |
| } | |
| @SuppressWarnings({"unchecked"}) | |
| private static void initRedisTemplate() { | |
| if (redisTemplate == null) { | |
| redisTemplate = AppContextUtil.getBean(RedisTemplate.class, String.class, Object.class); | |
| } | |
| } | |
| private static void checkKeyExists(String key, String errorMessage) { | |
| boolean exists = redisTemplate.opsForValue().get(KEY_PREFIX + key) != null; | |
| if (exists) { | |
| throw new RuntimeException(errorMessage + ", key: " + key); | |
| } | |
| } | |
| private static void writeKey(String key, Duration duration) { | |
| log.trace("write key: {}", key); | |
| redisTemplate.opsForValue().set(KEY_PREFIX + key, "1", duration); | |
| } | |
| private static void removeKey(String key) { | |
| log.trace("remove key: {}", key); | |
| redisTemplate.opsForValue().getAndDelete(KEY_PREFIX + key); | |
| } | |
| private static Supplier<Void> runnableToSupplier(Runnable action) { | |
| return () -> { | |
| action.run(); | |
| return null; | |
| }; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment