Skip to content

Commit 0b056b6

Browse files
authored
feature/v1.1.1: update bootstrapping and offline mode (#10)
1 parent 8fa03d0 commit 0b056b6

File tree

6 files changed

+45
-72
lines changed

6 files changed

+45
-72
lines changed

README.md

+9-20
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,8 @@ you will receive the client in an uninitialized state where feature flags will r
113113
continue trying to connect in the background unless there has been an `java.net.ProtocolException` or you close the
114114
client(using `close()`). You can detect whether initialization has succeeded by calling `isInitialized()`.
115115

116-
If `isInitialized()` returns `true`, you can use the client as normal. If it returns `false`, **_maybe SDK is not yet initialized
117-
or no feature flag has been set in your environment_**.
118-
119-
`isInitialized()` is optional, but it is recommended that you use it to avoid to get default values when the SDK is not yet initialized.
116+
If `isInitialized()` returns True, it means SDK has succeeded at some point in connecting to feature flag center,
117+
otherwise client has not yet connected to feature flag center, or has permanently failed. In this state, feature flag evaluations will always return default values.
120118

121119
```java
122120
FBConfig config = new FBConfig.Builder()
@@ -148,7 +146,10 @@ if (inited) {
148146
// the client is ready
149147
}
150148
```
151-
It's optional to wait for initialization to finish, but it is recommended that you do that to avoid to get default values when the SDK is not yet initialized.
149+
150+
`waitForOKState` method that will block until the client has successfully connected, or until the timeout expires.
151+
152+
> To check if the client is ready is optional. Even if the client is not ready, you can still evaluate feature flags, but the default value will be returned if SDK is not yet initialized.
152153
153154

154155
### FBConfig and Components
@@ -236,9 +237,6 @@ in real time, as mentioned in [Bootstrapping](#boostrapping).
236237
After initialization, the SDK has all the feature flags in the memory and all evaluation is done _**locally and
237238
synchronously**_, the average evaluation time is < _**10**_ ms.
238239

239-
If evaluation called before Java SDK client initialized, or you set the wrong flag key or user for the evaluation, SDK will return
240-
the default value you set.
241-
242240
There is a `variation` method that returns a flag value, and a `variationDetail` method that returns an object
243241
describing how the value was determined for each type.
244242

@@ -276,6 +274,9 @@ EvalDetail<String> detail = states.getStringDetail("flag key", user, "Not Found"
276274
String value = states.getString("flag key", user, "Not Found");
277275
```
278276

277+
> If evaluation called before Java SDK client initialized, you set the wrong flag key/user for the evaluation or the related feature flag
278+
is not found SDK will return the default value you set. `EvalDetail` will explain the details of the latest evaluation including error raison.
279+
279280
### Offline Mode
280281
In some situations, you might want to stop making remote calls to FeatBit. Here is how:
281282

@@ -329,15 +330,3 @@ client.trackMetric(user, eventName, numericValue);
329330
**numericValue** is not mandatory, the default value is **1**.
330331

331332
Make sure `trackMetric` is called after the related feature flag is called, otherwise the custom event won't be included into the experiment result.
332-
333-
334-
## Getting support
335-
336-
- If you have a specific question about using this sdk, we encourage you
337-
to [ask it in our slack](https://join.slack.com/t/featbit/shared_invite/zt-1ew5e2vbb-x6Apan1xZOaYMnFzqZkGNQ).
338-
- If you encounter a bug or would like to request a
339-
feature, [submit an issue](https://github.com/featbit/featbit-java-sdk/issues/new).
340-
341-
## See Also
342-
343-
- [Connect To Java Sdk](https://docs.featbit.co/docs/getting-started/4.-connect-an-sdk/server-side-sdks/java-sdk)

src/main/java/co/featbit/server/FBClientImp.java

+6-18
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,18 @@ public FBClientImp(String envSecret, FBConfig config) {
125125
if (!(config.getDataSynchronizerFactory() instanceof FactoryImp.NullDataSynchronizerFactory)) {
126126
logger.info("FB JAVA SDK: waiting for Client initialization in {} milliseconds", startWait.toMillis());
127127
}
128-
if (config.getDataStorageFactory() instanceof FactoryImp.NullDataStorageFactory) {
129-
logger.info("FB JAVA SDK: SDK just returns default variation");
128+
if (config.getDataStorageFactory() instanceof FactoryImp.NullDataStorageFactory
129+
|| (!dataUpdater.storageInitialized() && !offline)) {
130+
logger.info("SDK just returns default variation because of no data found in the given environment");
130131
}
131132
boolean initResult = initFuture.get(startWait.toMillis(), TimeUnit.MILLISECONDS);
132-
if (initResult && !offline) {
133-
logger.info("FB JAVA SDK: SDK initialization is completed");
134-
if (!this.dataSynchronizer.isInitialized()) {
135-
logger.warn("FB JAVA SDK: SDK was not completely initialized because of no data found in your environment");
136-
}
133+
if (!initResult) {
134+
logger.warn("FB JAVA SDK: SDK was not successfully initialized");
137135
}
138136
} catch (TimeoutException e) {
139137
logger.error("FB JAVA SDK: timeout encountered when waiting for data update");
140-
logger.warn("FB JAVA SDK: SDK was not successfully initialized");
141138
} catch (Exception e) {
142139
logger.error("FB JAVA SDK: exception encountered when waiting for data update", e);
143-
logger.warn("FB JAVA SDK: SDK was not successfully initialized");
144140
}
145141
} else {
146142
logger.info("FB JAVA SDK: SDK starts in asynchronous mode");
@@ -293,10 +289,6 @@ public void close() throws IOException {
293289
this.insightProcessor.close();
294290
}
295291

296-
public boolean isOffline() {
297-
return offline;
298-
}
299-
300292
@Override
301293
public Status.DataUpdateStatusProvider getDataUpdateStatusProvider() {
302294
return dataUpdateStatusProvider;
@@ -310,11 +302,7 @@ public boolean initializeFromExternalJson(String json) {
310302
DataModel.Data allData = all.data();
311303
Long version = allData.getTimestamp();
312304
Map<DataStorageTypes.Category, Map<String, DataStorageTypes.Item>> allDataInStorageType = allData.toStorageType();
313-
boolean res = dataUpdater.init(allDataInStorageType, version);
314-
if (res) {
315-
dataUpdater.updateStatus(Status.State.OKState());
316-
}
317-
return res;
305+
return dataUpdater.init(allDataInStorageType, version);
318306
}
319307
}
320308
return false;

src/main/java/co/featbit/server/FactoryImp.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,13 @@ private static final class NullDataSynchronizer implements DataSynchronizer {
128128

129129
@Override
130130
public Future<Boolean> start() {
131+
dataUpdater.updateStatus(Status.State.OKState());
131132
return CompletableFuture.completedFuture(Boolean.TRUE);
132133
}
133134

134135
@Override
135136
public boolean isInitialized() {
136-
return dataUpdater.storageInitialized();
137+
return true;
137138
}
138139

139140
@Override

src/main/java/co/featbit/server/Streaming.java

+3-14
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,7 @@ public Future<Boolean> start() {
104104

105105
@Override
106106
public boolean isInitialized() {
107-
boolean storageInit = updater.storageInitialized();
108-
boolean streamingInit = initialized.get();
109-
if (!storageInit) {
110-
logger.debug("data storage is empty");
111-
}
112-
if (!streamingInit) {
113-
logger.debug("streaming is not yet initialized");
114-
}
115-
return storageInit && streamingInit;
107+
return initialized.get();
116108
}
117109

118110
@Override
@@ -204,11 +196,8 @@ static Boolean processData(Status.DataUpdater updater, DataModel.Data data, Atom
204196
if (initialized.compareAndSet(false, true)) {
205197
initFuture.complete(true);
206198
}
207-
if (updater.storageInitialized()) {
208-
// if the storage is not yet initialized, keep the streaming in initializing state.
209-
logger.debug("processing data is well done");
210-
updater.updateStatus(Status.State.OKState());
211-
}
199+
logger.debug("processing data is well done");
200+
updater.updateStatus(Status.State.OKState());
212201
}
213202
return opOK;
214203
}

src/test/java/co/featbit/server/FBClientBaseTest.java

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.google.common.io.Resources;
55

66
import java.io.IOException;
7+
import java.time.Duration;
78
import java.util.Base64;
89

910
public abstract class FBClientBaseTest {
@@ -23,6 +24,18 @@ protected FBClientImp initClientInOfflineMode() throws IOException {
2324
return client;
2425
}
2526

27+
protected FBClientImp initClientWithNullComponents(Duration duration) {
28+
FBConfig config = new FBConfig.Builder()
29+
.startWaitTime(duration)
30+
.streamingURL("ws://fake-url")
31+
.eventURL("http://fake-url")
32+
.dataSynchronizerFactory(Factory.externalDataSynchronization())
33+
.insightProcessorFactory(Factory.externalEventTrack())
34+
.build();
35+
FBClientImp client = new FBClientImp("env-secret", config);
36+
return client;
37+
}
38+
2639
protected String readResource(final String fileName) throws IOException {
2740
return Resources.toString(Resources.getResource(fileName), Charsets.UTF_8);
2841
}

src/test/java/co/featbit/server/FBClientTest.java

+12-19
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ void testVariationArgumentError() throws IOException {
202202
@Test
203203
void testVariationWhenClientNotInitialized() throws Exception {
204204
expect(dataSynchronizer.start()).andReturn(initFuture);
205-
expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(false);
205+
expect(initFuture.get(anyLong(), anyObject(TimeUnit.class))).andReturn(false);
206206
expect(dataSynchronizer.isInitialized()).andReturn(false).anyTimes();
207207
support.replayAll();
208208
fakeConfigBuilder.startWaitTime(Duration.ofMillis(10));
@@ -215,6 +215,9 @@ void testVariationWhenClientNotInitialized() throws Exception {
215215
AllFlagStates states = client.getAllLatestFlagsVariations(user1);
216216
assertFalse(states.isSuccess());
217217
assertEquals(REASON_CLIENT_NOT_READY, states.getReason());
218+
ed = states.getBooleanDetail("ff-test-bool", false);
219+
assertFalse(ed.getVariation());
220+
assertEquals(REASON_FLAG_NOT_FOUND, ed.getReason());
218221
support.verifyAll();
219222
}
220223
}
@@ -223,7 +226,7 @@ void testVariationWhenClientNotInitialized() throws Exception {
223226
void testVariationThrowException() throws Exception {
224227
expect(dataStorage.get(anyObject(DataStorageTypes.Category.class), anyString())).andThrow(new RuntimeException("test exception"));
225228
expect(dataSynchronizer.start()).andReturn(initFuture);
226-
expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(true);
229+
expect(initFuture.get(anyLong(), anyObject(TimeUnit.class))).andReturn(true);
227230
expect(dataSynchronizer.isInitialized()).andReturn(true).anyTimes();
228231
support.replayAll();
229232
fakeConfigBuilder.startWaitTime(Duration.ofMillis(10));
@@ -284,33 +287,23 @@ void testConstructIllegalUrl() throws IOException {
284287

285288
@Test
286289
void testConstructStartWait() throws Exception {
287-
expect(dataSynchronizer.start()).andReturn(initFuture);
288-
expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(true);
289-
expect(dataSynchronizer.isInitialized()).andReturn(true).anyTimes();
290-
support.replayAll();
291-
fakeConfigBuilder.startWaitTime(Duration.ofMillis(10));
292-
try (FBClient client = createMockClient(fakeConfigBuilder)) {
290+
try (FBClient client = initClientWithNullComponents(Duration.ofMillis(10))) {
293291
assertTrue(client.isInitialized());
294-
support.verifyAll();
295292
}
296293
}
297294

298295
@Test
299-
void testConstructStartNoWait() throws IOException {
300-
expect(dataSynchronizer.start()).andReturn(initFuture);
301-
expect(dataSynchronizer.isInitialized()).andReturn(false).anyTimes();
302-
support.replayAll();
303-
fakeConfigBuilder.startWaitTime(Duration.ZERO);
304-
try (FBClient client = createMockClient(fakeConfigBuilder)) {
305-
assertFalse(client.isInitialized());
306-
support.verifyAll();
296+
void testConstructStartNoWait() throws Exception {
297+
try (FBClient client = initClientWithNullComponents(Duration.ZERO)) {
298+
assertTrue(client.getDataUpdateStatusProvider().waitForOKState(Duration.ofMillis(10)));
299+
assertTrue(client.isInitialized());
307300
}
308301
}
309302

310303
@Test
311304
void testConstructStartThrowTimeoutException() throws Exception {
312305
expect(dataSynchronizer.start()).andReturn(initFuture);
313-
expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andThrow(new TimeoutException("test exception"));
306+
expect(initFuture.get(anyLong(), anyObject(TimeUnit.class))).andThrow(new TimeoutException("test exception"));
314307
expect(dataSynchronizer.isInitialized()).andReturn(false).anyTimes();
315308
support.replayAll();
316309
fakeConfigBuilder.startWaitTime(Duration.ofMillis(10));
@@ -323,7 +316,7 @@ void testConstructStartThrowTimeoutException() throws Exception {
323316
@Test
324317
void testConstructStartThrowException() throws Exception {
325318
expect(dataSynchronizer.start()).andReturn(initFuture);
326-
expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andThrow(new InterruptedException("test exception"));
319+
expect(initFuture.get(anyLong(), anyObject(TimeUnit.class))).andThrow(new InterruptedException("test exception"));
327320
expect(dataSynchronizer.isInitialized()).andReturn(false).anyTimes();
328321
support.replayAll();
329322
fakeConfigBuilder.startWaitTime(Duration.ofMillis(10));

0 commit comments

Comments
 (0)