Skip to content

Commit 61b89a6

Browse files
authored
[FFM-1844]: FeatureSnapshot for JavaSDK (#199)
Added ability to store and retrieve feature snapshot, containing both previous and current version of the feature config.
1 parent 441577c commit 61b89a6

File tree

17 files changed

+829
-18
lines changed

17 files changed

+829
-18
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ Add the following Maven dependency in your project's pom.xml file:
7878
<dependency>
7979
<groupId>io.harness</groupId>
8080
<artifactId>ff-java-server-sdk</artifactId>
81-
<version>1.6.1</version>
81+
<version>1.7.0</version>
8282
</dependency>
8383
```
8484

8585
#### Gradle
8686

8787
```
88-
implementation 'io.harness:ff-java-server-sdk:1.6.1'
88+
implementation 'io.harness:ff-java-server-sdk:1.7.0'
8989
```
9090

9191
### Code Sample

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ plugins {
1818
id 'jacoco-report-aggregation'
1919
id "com.github.spotbugs" version libs.versions.spotbugs
2020
id "org.owasp.dependencycheck" version libs.versions.depcheck
21+
id 'me.champeau.jmh' version '0.6.8' // Added JMH plugin
2122
}
2223

2324

@@ -55,6 +56,10 @@ dependencies {
5556

5657
compileOnly libs.lombok
5758
annotationProcessor libs.lombok
59+
60+
// JMH dependencies
61+
implementation 'org.openjdk.jmh:jmh-core:1.37'
62+
annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
5863
}
5964

6065
group = 'io.harness'

examples/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ tasks.register('GettingStarted', JavaExec) {
1818
classpath = sourceSets.main.runtimeClasspath
1919
mainClass = "io.harness.ff.examples.GettingStarted"
2020
}
21+
22+
tasks.register('EventExample', JavaExec) {
23+
classpath = sourceSets.main.runtimeClasspath
24+
mainClass = "io.harness.ff.examples.EventExample"
25+
}
26+
27+
tasks.register('EventExampleWithFeatureSnapshot', JavaExec) {
28+
classpath = sourceSets.main.runtimeClasspath
29+
mainClass = "io.harness.ff.examples.EventExampleWithFeatureSnapshot"
30+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package io.harness.ff.examples;
2+
3+
import io.harness.cf.client.api.BaseConfig;
4+
import io.harness.cf.client.api.CfClient;
5+
import io.harness.cf.client.api.Event;
6+
import io.harness.cf.client.api.XmlFileMapStore;
7+
import io.harness.cf.client.connector.HarnessConfig;
8+
import io.harness.cf.client.connector.HarnessConnector;
9+
import io.harness.cf.client.dto.Target;
10+
import io.harness.cf.model.FeatureSnapshot;
11+
import lombok.extern.slf4j.Slf4j;
12+
13+
import java.util.List;
14+
import java.util.concurrent.Executors;
15+
import java.util.concurrent.ScheduledExecutorService;
16+
import java.util.concurrent.TimeUnit;
17+
18+
@Slf4j
19+
public class EventExampleWithFeatureSnapshot {
20+
private static final String SDK_KEY = "";
21+
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
22+
23+
private static CfClient client;
24+
25+
public static void main(String... args) {
26+
27+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
28+
scheduler.shutdown();
29+
client.close();
30+
}));
31+
32+
final XmlFileMapStore fileStore = new XmlFileMapStore("Non-Freemium");
33+
// this is one way of initialising config.
34+
final HarnessConnector hc = new HarnessConnector(SDK_KEY, HarnessConfig.builder().build());
35+
36+
BaseConfig bc = BaseConfig.builder().
37+
enableFeatureSnapshot(true).build();
38+
39+
// initialise the client.
40+
client = new CfClient(hc, bc);
41+
42+
client.on(Event.READY, result -> log.info("READY"));
43+
44+
// example: specified prefix we can filter on.
45+
final String FLAG_PREFIX = "FFM_";
46+
// example: given flag change event - get both previous and current feature if prefix is matched.
47+
client.on(Event.CHANGED, identifier -> {
48+
if (identifier.startsWith(FLAG_PREFIX)) {
49+
getSnapshot(identifier);
50+
} else {
51+
log.info("We had an event change but flag did not have required prefix");
52+
}
53+
});
54+
55+
// example : given flag change event - get all snapshots.
56+
client.on(Event.CHANGED, identifier -> {
57+
getAllSnapshots();
58+
});
59+
60+
// example : given flag change event - get all snapshots with given prefix.
61+
client.on(Event.CHANGED, identifier -> {
62+
getAllSnapshotsWithPrefix();
63+
});
64+
65+
final Target target = Target.builder().identifier("target1").attribute("testKey", "TestValue").name("target1").build();
66+
67+
scheduler.scheduleAtFixedRate(() -> {
68+
log.info("ticking...");
69+
}, 0, 10, TimeUnit.SECONDS);
70+
71+
}
72+
73+
74+
// example method to extract a single snapshot.
75+
private static void getSnapshot(String identifier) {
76+
log.info("We had a chang event and prefix matched, lets inspect the diff");
77+
// fetch current and previous version of the feature
78+
FeatureSnapshot snapshot = client.getFeatureSnapshot(identifier);
79+
log.info("Previous flag config: {}, {}", identifier, snapshot.getPrevious());
80+
log.info("Current flag config: {}, {}", identifier, snapshot.getCurrent());
81+
}
82+
83+
84+
// example method to extract and print all the snapshots.
85+
private static void getAllSnapshots() {
86+
// get all snapshots
87+
List<FeatureSnapshot> snapshots = client.getAllFeatureSnapshots();
88+
int counter = 0;
89+
for (FeatureSnapshot snapshot : snapshots) {
90+
log.info("snapshots {} {}", ++counter, snapshot);
91+
}
92+
}
93+
94+
// example method to extract and print all the snapshots.
95+
private static void getAllSnapshotsWithPrefix() {
96+
// get all snapshots
97+
String prefix = "FFM_";
98+
List<FeatureSnapshot> snapshots = client.getAllFeatureSnapshots(prefix);
99+
int counter = 0;
100+
for (FeatureSnapshot snapshot : snapshots) {
101+
log.info("snapshots {} {}", ++counter, snapshot);
102+
}
103+
}
104+
}
105+
106+

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ dependencyResolutionManagement {
44
versionCatalogs {
55
libs {
66
// main sdk version
7-
version('sdk', '1.6.1');
7+
version('sdk', '1.7.0');
88

99
// sdk deps
1010
version('okhttp3', '4.12.0')

src/main/java/io/harness/cf/client/api/BaseConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public class BaseConfig {
4242
/** If metrics service POST call is taking > this time, we need to know about it */
4343
@Builder.Default private final long metricsServiceAcceptableDuration = 10000;
4444

45+
/** store previous and current version of the FeatureConfig */
46+
@Builder.Default private final boolean enableFeatureSnapshot = false;
47+
4548
/** Get metrics post frequency in seconds */
4649
public int getFrequency() {
4750
return Math.max(frequency, Config.MIN_FREQUENCY);

src/main/java/io/harness/cf/client/api/CfClient.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import io.harness.cf.client.dto.Message;
66
import io.harness.cf.client.dto.Target;
77
import io.harness.cf.client.logger.LogUtil;
8+
import io.harness.cf.model.FeatureSnapshot;
9+
import java.util.List;
810
import java.util.function.Consumer;
911
import lombok.NonNull;
1012

@@ -83,6 +85,18 @@ public void on(@NonNull final Event event, @NonNull final Consumer<String> consu
8385
client.on(event, consumer);
8486
}
8587

88+
public List<FeatureSnapshot> getAllFeatureSnapshots(String prefix) {
89+
return client.getFeatureSnapshots(prefix);
90+
}
91+
92+
public List<FeatureSnapshot> getAllFeatureSnapshots() {
93+
return client.getFeatureSnapshots();
94+
}
95+
96+
public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) {
97+
return client.getFeatureSnapshot(identifier);
98+
}
99+
86100
public void off() {
87101
client.off();
88102
}

src/main/java/io/harness/cf/client/api/InnerClient.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import io.harness.cf.client.dto.Message;
77
import io.harness.cf.client.dto.Target;
88
import io.harness.cf.model.FeatureConfig;
9+
import io.harness.cf.model.FeatureSnapshot;
910
import io.harness.cf.model.Variation;
11+
import java.util.LinkedList;
12+
import java.util.List;
1013
import java.util.concurrent.ConcurrentHashMap;
1114
import java.util.concurrent.CopyOnWriteArrayList;
1215
import java.util.function.Consumer;
@@ -86,7 +89,9 @@ protected void setUp(@NonNull final Connector connector, @NonNull final BaseConf
8689
this.connector.setOnUnauthorized(this::onUnauthorized);
8790

8891
// initialization
89-
repository = new StorageRepository(options.getCache(), options.getStore(), this);
92+
repository =
93+
new StorageRepository(
94+
options.getCache(), options.getStore(), this, options.isEnableFeatureSnapshot());
9095
evaluator = new Evaluator(repository, options);
9196
authService = new AuthService(this.connector, options.getPollIntervalInSeconds(), this);
9297
pollProcessor =
@@ -307,6 +312,32 @@ public synchronized void waitForInitialization()
307312
}
308313
}
309314

315+
public List<FeatureSnapshot> getFeatureSnapshots() {
316+
return getFeatureSnapshots("");
317+
}
318+
319+
public List<FeatureSnapshot> getFeatureSnapshots(String prefix) {
320+
if (!options.isEnableFeatureSnapshot()) {
321+
log.debug("FeatureSnapshot disabled, snapshot will contain only current version.");
322+
}
323+
List<String> identifiers = repository.getAllFeatureIdentifiers(prefix);
324+
List<FeatureSnapshot> snapshots = new LinkedList<>();
325+
326+
for (String identifier : identifiers) {
327+
FeatureSnapshot snapshot = getFeatureSnapshot(identifier);
328+
snapshots.add(snapshot);
329+
}
330+
331+
return snapshots;
332+
}
333+
334+
public FeatureSnapshot getFeatureSnapshot(@NonNull String identifier) {
335+
if (!options.isEnableFeatureSnapshot()) {
336+
log.debug("FeatureSnapshot disabled, snapshot will contain only current version.");
337+
}
338+
return repository.getFeatureSnapshot(identifier);
339+
}
340+
310341
public void on(@NonNull final Event event, @NonNull final Consumer<String> consumer) {
311342
final CopyOnWriteArrayList<Consumer<String>> consumers =
312343
events.getOrDefault(event, new CopyOnWriteArrayList<>());

src/main/java/io/harness/cf/client/api/Query.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.harness.cf.client.api;
22

33
import io.harness.cf.model.FeatureConfig;
4+
import io.harness.cf.model.FeatureSnapshot;
45
import io.harness.cf.model.Segment;
56
import java.util.List;
67
import java.util.Optional;
@@ -13,4 +14,8 @@ public interface Query {
1314
Optional<Segment> getSegment(@NonNull String identifier);
1415

1516
List<String> findFlagsBySegment(@NonNull String identifier);
17+
18+
FeatureSnapshot getFeatureSnapshot(@NonNull String identifier);
19+
20+
List<String> getAllFeatureIdentifiers(String prefix);
1621
}

0 commit comments

Comments
 (0)