From 146c1fa24a37d8756c2c88404db24939adf0b885 Mon Sep 17 00:00:00 2001 From: Roger Floriano <31597636+petruki@users.noreply.github.com> Date: Fri, 26 Apr 2024 21:22:07 -0700 Subject: [PATCH 1/2] Fixes snapshot scheduler to prevent duplicate initialization (#291) * Fixes snapshot scheduler to prevent duplicate initialization * chore: added test for null strategy entry --- README.md | 56 +++++++++++-------- .../client/SwitcherContextBase.java | 14 +++-- .../client/service/WorkerName.java | 1 - .../service/local/ClientLocalService.java | 7 ++- .../client/utils/SnapshotWatcher.java | 17 +++--- .../client/utils/SwitcherUtils.java | 10 ++-- .../client/SwitcherLocal1Test.java | 11 ++++ .../SwitcherSnapshotAutoUpdateTest.java | 24 +++++++- 8 files changed, 92 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index a9cb7ce2..189ae63a 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ switcher.isItOn(); ``` 3. **Strategy validation - Fluent style** -Create chained calls using 'getSwitcher' then 'prepareEntry' then 'isItOn' functions. +Create chained calls to validate the switcher with a more readable and maintainable code. ```java import static **.MyAppFeatures.*; @@ -165,32 +165,22 @@ getSwitcher(FEATURE01) .isItOn(); ``` -4. **Strategy validation - all-in-one execution** -All-in-one method is fast and include everything you need to execute a complex call to the API. Stack inputs changing the last parameter to *true* in case you need to add more values to the strategy validator. - -```java -switcher.isItOn(FEATURE01, Entry.build(StrategyValidator.NETWORK, "10.0.0.3"), false); -//or simply -switcher.checkNetwork("10.0.0.3").isItOn(); -``` - -5. **Accessing the response history** -Switchers when created store the last execution result from a given switcher key. This can be useful for troubleshooting or internal logging. +4. **Accessing the response history** +Switchers stores the last execution result from a given switcher key/entry. ```java switcher.getHistoryExecution(); ``` -6. **Throttling** -Improve the overall performance by using throttle feature to skip API calls in a short time. This feature is ideal for critical or repetitive code executions that requires high performance. +5. **Throttling** +Run Switchers asynchronously when using throttling. It will return the last known value until the throttle time is over. ```java switcher.throttle(1000).isItOn(); ``` ## Local settings -You can also set the Switcher library to work locally. It will use a local snapshot file to retrieve the switchers configuration.
-This feature is useful for testing purposes or when you need to run your application without internet access. +You can also set the Switcher library to work locally. It will use a local snapshot file to retrieve the switchers configuration. ```java MyAppFeatures.configure(ContextBuilder.builder() @@ -207,21 +197,39 @@ switcher.isItOn(); Forcing Switchers to resolve remotely can help you define exclusive features that cannot be resolved locally.
This feature is ideal if you want to run the SDK in local mode but still want to resolve a specific switcher remotely. +```java +switcher.forceRemote().isItOn(); +``` + +Another option is to use in-memory loaded snapshots to resolve the switchers.
+Switcher SDK will schedule a background task to update snapshot in-memory a new version is available. + ```java MyAppFeatures.configure(ContextBuilder.builder() - .url("https://switcher-api.com") - .apiKey("API_KEY") + .url("https://api.switcherapi.com") + .apiKey("[API-KEY]") .domain("Playground") - .component("switcher-playground") - .local(true) - .snapshotLocation("/src/resources")); + .local(true) + .snapshotAutoLoad(true) + .snapshotAutoUpdateInterval("5s") // You can choose to configure here or using `scheduleSnapshotAutoUpdate` + .component("switcher-playground")); MyAppFeatures.initializeClient(); - -Switcher switcher = MyAppFeatures.getSwitcher(FEATURE01); -switcher.forceRemote().isItOn(); +MyAppFeatures.scheduleSnapshotAutoUpdate("5s", new SnapshotCallback() { + @Override + public void onSnapshotUpdate(long version) { + logger.info("Snapshot updated: {}", version); + } + + @Override + public void onSnapshotUpdateError(Exception e) { + logger.error("Failed to update snapshot: {}", e.getMessage()); + } +}); ``` + + ## Real-time snapshot updater Let the Switcher Client manage your application local snapshot.
These features allow you to configure the SDK to automatically update the snapshot in the background. diff --git a/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java index 99a8c212..3fc5b5c5 100644 --- a/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java @@ -170,10 +170,11 @@ private static void loadSwitchers() { * * @param intervalValue to be used for the update (e.g. 5s, 1m, 1h, 1d) * @param callback to be invoked when the snapshot is updated or when an error occurs + * @return true if the task was scheduled successfully */ - public static void scheduleSnapshotAutoUpdate(String intervalValue, SnapshotCallback callback) { - if (StringUtils.isBlank(intervalValue)) { - return; + public static boolean scheduleSnapshotAutoUpdate(String intervalValue, SnapshotCallback callback) { + if (StringUtils.isBlank(intervalValue) || scheduledExecutorService != null) { + return false; } final long interval = SwitcherUtils.getMillis(intervalValue); @@ -191,6 +192,7 @@ public static void scheduleSnapshotAutoUpdate(String intervalValue, SnapshotCall initExecutorService(); scheduledExecutorService.scheduleAtFixedRate(runnableSnapshotValidate, 0, interval, TimeUnit.MILLISECONDS); + return true; } /** @@ -198,9 +200,10 @@ public static void scheduleSnapshotAutoUpdate(String intervalValue, SnapshotCall * The task will be executed in a single thread executor service. * * @param intervalValue to be used for the update (e.g. 5s, 1m, 1h, 1d) + * @return true if the task was scheduled successfully */ - public static void scheduleSnapshotAutoUpdate(String intervalValue) { - scheduleSnapshotAutoUpdate(intervalValue, null); + public static boolean scheduleSnapshotAutoUpdate(String intervalValue) { + return scheduleSnapshotAutoUpdate(intervalValue, null); } /** @@ -335,6 +338,7 @@ public static void configure(ContextBuilder builder) { public static void terminateSnapshotAutoUpdateWorker() { if (scheduledExecutorService != null) { scheduledExecutorService.shutdownNow(); + scheduledExecutorService = null; } } diff --git a/src/main/java/com/github/switcherapi/client/service/WorkerName.java b/src/main/java/com/github/switcherapi/client/service/WorkerName.java index 3a4c132f..08fd04d4 100644 --- a/src/main/java/com/github/switcherapi/client/service/WorkerName.java +++ b/src/main/java/com/github/switcherapi/client/service/WorkerName.java @@ -2,7 +2,6 @@ public enum WorkerName { - REGEX_VALIDATOR_WORKER("switcherapi-regex-validator"), SNAPSHOT_WATCH_WORKER("switcherapi-snapshot-watcher"), SNAPSHOT_UPDATE_WORKER("switcherapi-snapshot-update"); diff --git a/src/main/java/com/github/switcherapi/client/service/local/ClientLocalService.java b/src/main/java/com/github/switcherapi/client/service/local/ClientLocalService.java index 38156b56..283d9fd1 100644 --- a/src/main/java/com/github/switcherapi/client/service/local/ClientLocalService.java +++ b/src/main/java/com/github/switcherapi/client/service/local/ClientLocalService.java @@ -123,8 +123,8 @@ public CriteriaResponse executeCriteria(final Switcher switcher, final Domain do */ private CriteriaResponse processOperation(final Strategy[] configStrategies, final List input, final Switcher switcher) { - SwitcherUtils.debug(logger, "configStrategies: {}", () -> Arrays.toString(configStrategies)); - SwitcherUtils.debug(logger, "input: {}", () -> Arrays.toString(input != null ? input.toArray() : ArrayUtils.EMPTY_STRING_ARRAY)); + SwitcherUtils.debugSupplier(logger, "configStrategies: {}", Arrays.toString(configStrategies)); + SwitcherUtils.debugSupplier(logger, "input: {}", Arrays.toString(input != null ? input.toArray() : ArrayUtils.EMPTY_STRING_ARRAY)); boolean result; for (final Strategy strategy : configStrategies) { @@ -153,8 +153,9 @@ private CriteriaResponse strategyFailed(Switcher switcher, Strategy strategy, St } private Entry tryGetSwitcherInput(final List input, Strategy strategy) { - if (input == null) + if (input == null) { return null; + } return input.stream() .filter(i -> i.getStrategy().equals(strategy.getStrategy())) diff --git a/src/main/java/com/github/switcherapi/client/utils/SnapshotWatcher.java b/src/main/java/com/github/switcherapi/client/utils/SnapshotWatcher.java index 65296a5c..1af79475 100644 --- a/src/main/java/com/github/switcherapi/client/utils/SnapshotWatcher.java +++ b/src/main/java/com/github/switcherapi/client/utils/SnapshotWatcher.java @@ -57,14 +57,14 @@ public void run() { WatchEvent ev = (WatchEvent) event; Path filename = ev.context(); - if (executorInstance != null) - executorInstance.notifyChange(filename.toString(), handler); + if (executorInstance != null) { + executorInstance.notifyChange(filename.toString(), handler); + } } - - boolean valid = key.reset(); - - if (!valid) - break; + + if (!key.reset()) { + break; + } } } catch (IOException | InterruptedException | ClosedWatchServiceException e) { Thread.currentThread().interrupt(); @@ -74,8 +74,9 @@ public void run() { public void terminate() { try { - if (watcher != null) + if (watcher != null) { watcher.close(); + } } catch (IOException e) { logger.error(e); } diff --git a/src/main/java/com/github/switcherapi/client/utils/SwitcherUtils.java b/src/main/java/com/github/switcherapi/client/utils/SwitcherUtils.java index a065711f..e54b1558 100644 --- a/src/main/java/com/github/switcherapi/client/utils/SwitcherUtils.java +++ b/src/main/java/com/github/switcherapi/client/utils/SwitcherUtils.java @@ -21,7 +21,6 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -143,8 +142,9 @@ public static Set payloadReader(String jsonStr, String prevKey) { * @param handler to notify snapshot change events */ public static void watchSnapshot(final SwitcherExecutor executorInstance, SnapshotEventHandler handler) { - if (watcher == null) + if (watcher == null) { watcher = new SnapshotWatcher(executorInstance, handler); + } initExecutorService(); executorService.submit(watcher); @@ -211,14 +211,14 @@ public static void debug(Logger logger, String message, Object... args) { * @param message to be logged * @param paramSuppliers parameters to be replaced in the message */ - public static void debug(Logger logger, String message, Supplier paramSuppliers) { + public static void debugSupplier(Logger logger, String message, Object paramSuppliers) { if (logger.isDebugEnabled()) { - logger.debug(message, paramSuppliers); + logger.debug(message, () -> paramSuppliers); } } /** - * Resolve environment variable 'value'and extract its value from either + * Resolve environment variable 'value' and extract its value from either * System environment or default argument. * * @param value assigned from the properties file diff --git a/src/test/java/com/github/switcherapi/client/SwitcherLocal1Test.java b/src/test/java/com/github/switcherapi/client/SwitcherLocal1Test.java index 342a3300..cdea8d6b 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherLocal1Test.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherLocal1Test.java @@ -18,6 +18,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.nio.file.Paths; +import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -75,6 +76,16 @@ void localShouldNotReturn_keyNotFound() { Switcher switcher = Switchers.getSwitcher(Switchers.NOT_FOUND_KEY); assertThrows(SwitcherKeyNotFoundException.class, switcher::isItOn); } + + @Test + void localShouldReturnFalse_nullEntryForStrategy() { + Switcher switcher = Switchers.getSwitcher(Switchers.USECASE31); + + List entry = null; + assertFalse(switcher + .prepareEntry(entry) + .isItOn()); + } static Stream dateTestArguments() { return Stream.of( diff --git a/src/test/java/com/github/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java b/src/test/java/com/github/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java index 388894e8..aac904aa 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java @@ -20,8 +20,7 @@ import java.nio.file.Paths; import java.util.Date; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class SwitcherSnapshotAutoUpdateTest { @@ -261,4 +260,25 @@ void shouldNotKillThread_whenAPI_wentLocal() { assertEquals(2, Switchers.getSnapshotVersion()); } + @Test + @Order(6) + void shouldPreventSnapshotAutoUpdateToStart_whenAlreadySetup() { + //given + givenResponse(generateMockAuth()); //auth + givenResponse(generateSnapshotResponse("default.json")); //graphql + + //that + Switchers.configure(ContextBuilder.builder() + .url(String.format("http://localhost:%s", mockBackEnd.getPort())) + .snapshotLocation(null) + .environment("generated_mock_default_6") + .local(true) + .snapshotAutoLoad(true) + .snapshotAutoUpdateInterval("1s")); + + Switchers.initializeClient(); + boolean started = Switchers.scheduleSnapshotAutoUpdate("1m"); + assertFalse(started); + } + } From d38606dbb952b0d4ad272174727c027ae4004945 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 26 Apr 2024 21:32:04 -0700 Subject: [PATCH 2/2] Reverted worker constant name used in v1 --- .../java/com/github/switcherapi/client/service/WorkerName.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/switcherapi/client/service/WorkerName.java b/src/main/java/com/github/switcherapi/client/service/WorkerName.java index 08fd04d4..3a4c132f 100644 --- a/src/main/java/com/github/switcherapi/client/service/WorkerName.java +++ b/src/main/java/com/github/switcherapi/client/service/WorkerName.java @@ -2,6 +2,7 @@ public enum WorkerName { + REGEX_VALIDATOR_WORKER("switcherapi-regex-validator"), SNAPSHOT_WATCH_WORKER("switcherapi-snapshot-watcher"), SNAPSHOT_UPDATE_WORKER("switcherapi-snapshot-update");