diff --git a/README.md b/README.md index df6daed..24d7fca 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,81 @@ -# Go server Side SDK +# FeatBit Server-Side SDK for Go ## Introduction -This is the Go Server Side SDK for the feature management platform FeatBit. It is intended for use in a multi-user Go -server applications. +This is the Go Server-Side SDK for the 100% open-source feature flags management +platform [FeatBit](https://github.com/featbit/featbit). -This SDK has two main purposes: +The FeatBit Server-Side SDK for Go is designed primarily for use in multi-user systems such as web servers and +applications. -- Store the available feature flags and evaluate the feature flag variation for a given user -- Send feature flag usage and custom events for the insights and A/B/n testing. +## Data synchronization -## Data synchonization +We use websocket to make the local data synchronized with the FeatBit server, and then store them in memory by +default. Whenever there is any change to a feature flag or its related data, this change will be pushed to the SDK and +the average synchronization time is less than 100 ms. Be aware the websocket connection may be interrupted due to +internet outage, but it will be resumed automatically once the problem is gone. -We use websocket to make the local data synchronized with the server, and then store them in memory by default.Whenever -there is any change to a feature flag or its related data, this change will be pushed to the SDK, the average -synchronization time is less than **100** ms. Be aware the websocket connection may be interrupted due to internet -outage, but it will be resumed automatically once the problem is gone. +If you want to use your own data source, see [Offline Mode](#offline-mode). -## Offline mode support +## Get Started -In the offline mode, SDK DOES not exchange any data with your feature management platform +Go Server Side SDK is based on go 1.13, so you need to install go 1.13 or above. -In the following situation, the SDK would work when there is no internet connection: it has been initialized in -using `featbit.FBClient.InitializeFromExternalJson()` - -To open the offline mode: - -```go -config := featbit.DefaultFBConfig -featbit.Offline = true - -// or - -config := featbit.FBConfig{Offline: true} +### Installation +``` +go get github.com/featbit/featbit-go-sdk ``` -## Evaluation of a feature flag - -SDK will initialize all the related data(feature flags, segments etc.) in the bootstrapping and receive the data updates -in real time, as mentioned in the above. - -After initialization, the SDK has all the feature flags in the memory and all evaluation is done locally and -synchronously, the average evaluation time is < **10** ms. +### Quick Start +> Note that the _**envSecret**_, _**streamUrl**_ and _**eventUrl**_ are required to initialize the SDK. -## Installation +The following code demonstrates basic usage of the SDK. +```go +package main + +import ( + "fmt" + "github.com/featbit/featbit-go-sdk" + "github.com/featbit/featbit-go-sdk/interfaces" +) + +func main() { + envSecret := "" + streamingUrl := "ws://localhost:5100" + eventUrl := "http://localhost:5100" + + client, err := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) + + defer func() { + if client != nil { + // ensure that the SDK shuts down cleanly and has a chance to deliver events to FeatBit before the program exits + _ = client.Close() + } + }() + + if err == nil && client.IsInitialized() { + user, _ := interfaces.NewUserBuilder("").UserName("").Build() + _, ed, _ := client.BoolVariation("", user, false) + fmt.Printf("flag %s, returns %s for user %s, reason: %s \n", ed.KeyName, ed.Variation, user.GetKey(), ed.Reason) + } else { + fmt.Println("SDK initialization failed") + } +} ``` -go get github.com/featbit/featbit-go-sdk -``` -## SDK +### Examples + +- [Go Demo](https://github.com/featbit/featbit-samples/blob/main/samples/dino-game/demo-golang/go_demo.go) ### FBClient -Applications SHOULD instantiate a single instance for the lifetime of the application. In the case where an application +Applications **SHOULD instantiate a single FBClient instance** for the lifetime of the application. In the case where an application needs to evaluate feature flags from different environments, you may create multiple clients, but they should still be retained for the lifetime of the application rather than created per request or per thread. -### Bootstrapping +#### Bootstrapping The bootstrapping is in fact the call of constructor of `featbit.FBClient`, in which the SDK will be initialized, using streaming from your feature management platform. @@ -70,11 +87,23 @@ you will receive the client in an uninitialized state where feature flags will r continue trying to connect in the background unless there has been an `net.DNSError` or you close the client. You can detect whether initialization has succeeded by calling `featbit.FBClient.IsInitialized()`. -```go +If `featbit.FBClient.IsInitialized()` returns True, it means the `featbit.FBClient` has succeeded at some point in connecting to feature flag center and +has received feature flag data. + +If `featbit.FBClient.IsInitialized()` returns false, it means the client has not yet connected to feature flag center, or has permanently +failed. In this state, feature flag evaluations will always return default values. Another state that will cause this to return false is if the interfaces.DataStorage is empty. +It's strongly recommended to create at least one feature flag in your environment before using the SDK. -client, _ := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) -if !client.IsInitialized() { -// do whatever is appropriate if initialization has timed out +`featbit.FBClient.IsInitialized()` is optional, but it is recommended that you use it to avoid to get default values when the SDK is not yet initialized. + +```go +config := featbit.FBConfig{StartWait: 10 * time.Second} +// DO NOT forget to close the client when you don't need it anymore +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) +if err == nil && client.IsInitialized() { + // the client is ready +} else { + // the client is not ready } ``` @@ -84,25 +113,25 @@ point, you can use `featbit.FBClient.GetDataUpdateStatusProvider()`, which provi ```go config := featbit.FBConfig{StartWait: 0} -client, _ := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) -// later... +// DO NOT forget to close the client when you don't need it anymore +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) +if err != nil { + return +} ok := client.GetDataSourceStatusProvider().WaitForOKState(10 * time.Second) -if !ok { -// do whatever is appropriate if initialization has timed out +if ok { + // the client is ready +} else { + // the client is not ready } - ``` -Note that the _**sdkKey(envSecret)**_ is mandatory. - -### FBClient, FBConfig and Components +### FBConfig and Components -In the most case, you don't need to care about `featbit.FBConfig` and the internal components, just initialize SDK like: +In most cases, you don't need to care about `featbit.FBConfig` and the internal components, just initialize SDK like: ```go - -client, _ := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) - +client, err := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) ``` `envSecret` _**sdkKey(envSecret)**_ is id of your project in FeatBit feature flag center @@ -111,20 +140,6 @@ client, _ := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) `eventURL`: URL of your feature management platform to send analytics events -If you would like to run in the offline mode or change the timeout: - -```go -config := featbit.DefaultFBConfig -featbit.Offline = true -featbit.StartWait = 0 - -// or -config := featbit.FBConfig{StartWait: 0, Offline: true} - -client, _ := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) - -``` - `StartWait`: how long the constructor will block awaiting a successful data sync. Setting this to a zero or negative duration will not block and cause the constructor to return immediately. @@ -139,15 +154,15 @@ HTTP Proxy, TLS etc. `factories.NetworkBuilder` is the default `NetworkFactory` ```go - factory := factories.NewNetworkBuilder() factory.ProxyUrl("http://username:password@146.137.9.45:65233") config := featbit.DefaultFBConfig config.NetworkFactory = factory +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, *config) // or config := featbit.FBConfig{NetworkFactory: factory} - +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) ``` `DataStorageFactory` sets the implementation of `interfaces.DataStorage` to be used for holding feature flags and @@ -165,12 +180,9 @@ If Developers would like to know what the implementation is, they can read the G It's not recommended to change the default factories in the `featbit.FBConfig` -### Evaluation +### FBUser -SDK calculates the value of a feature flag for a given user, and returns a flag value/an object that describes the way -that the value was determined. - -`FBUser`: A collection of attributes that can affect flag evaluation, usually corresponding to a user of your +A collection of attributes that can affect flag evaluation, usually corresponding to a user of your application. This object contains built-in properties(`key`, `userName`). The `key` and `userName` are required. The `key` must uniquely identify each user; this could be a username or email address for authenticated users, or an ID @@ -179,32 +191,99 @@ The `userName` is used to search your user quickly. You may also define custom properties with arbitrary names and values. ```go -client, _ := featbit.NewFBClient(envSecret, streamingUrl, eventUrl) - // FBUser creation -user, _ := NewUserBuilder("key").UserName("name").Custom("property", "value").Build() - -// be sure that SDK is initialized -// this is not required -if(client.isInitialized()){ -// Flag value -// returns a string variation -variation, detail, _ := client.Variation("flag key", user, "Not Found"); - -// get all variations for a given user in your project -AllFlagStates states = client.AllLatestFlagsVariations(user); -variation, detail, _ = states.GetStringVariation("flag key", user, "Not Found"); +user, err := NewUserBuilder("key").UserName("name").Custom("property", "value").Build() +``` + +### Evaluation + +SDK calculates the value of a feature flag for a given user, and returns a flag value and `interfaces.EvalDetail` that describes the way +that the value was determined. + +SDK will initialize all the related data(feature flags, segments etc.) in the bootstrapping and receive the data updates +in real time, as mentioned in [Bootstrapping](#bootstrapping). + +After initialization, the SDK has all the feature flags in the memory and all evaluation is done _**locally and +synchronously**_, the average evaluation time is < _**10**_ ms. + +If evaluation called before Go SDK client initialized, or you set the wrong flag key or user for the evaluation, SDK will return +the default value you set. + +SDK supports String, Boolean, and Number and Json as the return type of flag values: + +- Variation(for string) +- BoolVariation +- IntVariation +- DoubleVariation +- JsonVariation + +```go +// be sure that SDK is initialized before evaluation +// DO not forget to close client when you are done with it +if client.isInitialized() { + // Flag value + // returns a string variation + variation, detail, _ := client.Variation("flag key", user, "Not Found") } ``` -If evaluation called before Go SDK client initialized, or you set the wrong flag key or user for the evaluation, SDK -will return the default value you set. +`featbit.FBClient.AllLatestFlagsVariations(user)` returns all variations for a given user. You can retrieve the flag value or details +for a specific flag key: + +- GetStringVariation +- GetBoolVariation +- GetIntVariation +- GetDoubleVariation +- GetJsonVariation -SDK supports String, Boolean, and Number and Json as the return type of flag values, see GoDocs for more details. +```go +// be sure that SDK is initialized before evaluation +// DO not forget to close client when you are done with it +if client.isInitialized() { + // get all variations for a given user in your project + allState, _ := client.AllLatestFlagsVariations(user) + variation, detail, _ := allState.GetStringVariation("flag key", "Not Found") +} +``` + +### Offline Mode + +In some situations, you might want to stop making remote calls to FeatBit. Here is how: + +```go +config := featbit.DefaultFBConfig +featbit.Offline = true +featbit.StartWait = 1 * time.Millisecond +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, *config) +// or +config := FBConfig{Offline: true, StartWait: 1 * time.Millisecond} +client, err := featbit.MakeCustomFBClient(envSecret, streamingUrl, eventUrl, config) + +``` + +When you put the SDK in offline mode, no insight message is sent to the server and all feature flag evaluations return +fallback values because there are no feature flags or segments available. If you want to use your own data source, +SDK allows users to populate feature flags and segments data from a JSON string. Here is an example: [fbclient_test_data.json](fixtures/fbclient_test_data.json). + +The format of the data in flags and segments is defined by FeatBit and is subject to change. Rather than trying to +construct these objects yourself, it's simpler to request existing flags directly from the FeatBit server in JSON format +and use this output as the starting point for your file. Here's how: + +```shell +# replace http://localhost:5100 with your evaluation server url +curl -H "Authorization: " http://localhost:5100/api/public/sdk/server/latest-all > featbit-bootstrap.json +``` + +Then you can use this file to initialize the SDK in offline mode: + +```go +// first load data from file and then +ok, _ := client.InitializeFromExternalJson(string(jsonBytes)) +``` ### Experiments (A/B/n Testing) -We support automatic experiments for pageviews and clicks, you just need to set your experiment on FeatBit platform, +We support automatic experiments for page-views and clicks, you just need to set your experiment on FeatBit platform, then you should be able to see the result in near real time after the experiment is started. In case you need more control over the experiment data sent to our server, we offer a method to send custom event. @@ -220,3 +299,12 @@ Make sure `featbit.FBClient.TrackPercentageMetric()` or `featbit.FBClient.TrackN otherwise the custom event may not be included into the experiment result. +## Getting support + +- If you have a specific question about using this sdk, we encourage you + to [ask it in our slack](https://join.slack.com/t/featbit/shared_invite/zt-1ew5e2vbb-x6Apan1xZOaYMnFzqZkGNQ). +- If you encounter a bug or would like to request a + feature, [submit an issue](https://github.com/featbit/featbit-go-sdk/issues/new). + +## See Also +- [Connect To Go Sdk](https://docs.featbit.co/docs/getting-started/4.-connect-an-sdk/server-side-sdks/go-sdk) diff --git a/eval_result.go b/eval_result.go index bc056d5..67b8497 100644 --- a/eval_result.go +++ b/eval_result.go @@ -113,11 +113,8 @@ func (er *evalResult) checkType(requiredType string) bool { case FlagBoolType: return requiredType == FlagBoolType || requiredType == FlagStringType case FlagNumericType: - if requiredType == FlagBoolType { - _, err := strconv.ParseFloat(er.fv, 64) - return err == nil - } - return true + _, err := strconv.ParseFloat(er.fv, 64) + return err == nil case FlagJsonType, FlagStringType: if requiredType == FlagBoolType { _, err := strconv.ParseBool(er.fv) diff --git a/fbclient.go b/fbclient.go index 9cf7bae..621f45e 100644 --- a/fbclient.go +++ b/fbclient.go @@ -204,10 +204,14 @@ func MakeCustomFBClient(envSecret string, streamingUrl string, eventUrl string, } select { case <-ready: - if !client.IsInitialized() && !config.Offline { + if !client.dataSynchronizer.IsInitialized() && !config.Offline { log.LogWarn("FB GO SDK: SDK was not successfully initialized") return client, initializationFailed } + if !client.dataUpdater.StorageInitialized() && !config.Offline { + log.LogWarn("FB GO SDK: SDK was not completely initialized because of no data found in your environment") + return client, nil + } log.LogInfo("FB GO SDK: SDK initialization is completed") return client, nil case <-time.After(config.StartWait): @@ -224,7 +228,7 @@ func MakeCustomFBClient(envSecret string, streamingUrl string, eventUrl string, } // IsInitialized tests whether the client is ready to be used. -// return true if the client is ready, or false if it is still initializing +// return true if the client is ready, or false if it is still initializing/interfaces.DataStorage is empty. // // If this value is true, it means the FBClient has succeeded at some point in connecting to feature flag center and // has received feature flag data. It could still have encountered a connection problem after that point, so @@ -233,11 +237,13 @@ func MakeCustomFBClient(envSecret string, streamingUrl string, eventUrl string, // If this value is false, it means the client has not yet connected to feature flag center, or has permanently // failed. In this state, feature flag evaluations will always return default values. You can use FBClient.GetDataUpdateStatusProvider // to get information on errors, or to wait for a successful retry. +// Another state that will cause this to return false is if the interfaces.DataStorage is empty. It's strongly recommended to create at least one feature flag in your environment +// before using the SDK. func (client *FBClient) IsInitialized() bool { - if client.dataSynchronizer == nil { + if client.dataSynchronizer == nil || client.dataUpdater == nil { return false } - return client.dataSynchronizer.IsInitialized() + return client.dataSynchronizer.IsInitialized() && client.dataUpdater.StorageInitialized() } // Close shuts down the FBClient. After calling this, the FBClient should no longer be used. diff --git a/fbclient_test.go b/fbclient_test.go index 2ca1980..27c2db2 100644 --- a/fbclient_test.go +++ b/fbclient_test.go @@ -3,6 +3,7 @@ package featbit import ( "encoding/base64" "encoding/json" + "github.com/featbit/featbit-go-sdk/factories" "github.com/featbit/featbit-go-sdk/fixtures" "github.com/featbit/featbit-go-sdk/interfaces" "github.com/featbit/featbit-go-sdk/internal/datastorage" @@ -51,25 +52,57 @@ func TestFBClientBootStrap(t *testing.T) { config := FBConfig{ StartWait: 200 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 100*time.Millisecond), - InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(insight2.NewMockSender(), 100, 100*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 100*time.Millisecond), + InsightProcessorFactory: factories.ExternalEventTrack(), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) require.NoError(t, err) assert.True(t, client.IsInitialized()) _ = client.Close() }) + t.Run("start and wait but fail in initialization", func(t *testing.T) { + config := FBConfig{ + StartWait: 200 * time.Millisecond, + DataStorageFactory: datastorage.NewMockDataStorageBuilder(), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(false, true, 100*time.Millisecond), + InsightProcessorFactory: factories.ExternalEventTrack(), + } + client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) + assert.Equal(t, err, initializationFailed) + assert.False(t, client.IsInitialized()) + res, detail, err := client.Variation("ff-test-string", testUser1, "error") + assert.Equal(t, err, clientNotInitialized) + assert.Equal(t, ReasonClientNotReady, detail.Reason) + assert.Equal(t, "error", res) + _ = client.Close() + }) + t.Run("start and wait but no data loaded", func(t *testing.T) { + config := FBConfig{ + StartWait: 200 * time.Millisecond, + DataStorageFactory: datastorage.NewMockDataStorageBuilder(), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, false, 100*time.Millisecond), + InsightProcessorFactory: factories.ExternalEventTrack(), + } + client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) + require.NoError(t, err) + assert.False(t, client.IsInitialized()) + res, detail, err := client.Variation("ff-test-string", testUser1, "error") + assert.Equal(t, err, clientNotInitialized) + assert.Equal(t, ReasonClientNotReady, detail.Reason) + assert.Equal(t, "error", res) + _ = client.Close() + }) t.Run("start and no wait", func(t *testing.T) { config := FBConfig{ StartWait: 0, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 100*time.Millisecond), - InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(insight2.NewMockSender(), 100, 100*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 100*time.Millisecond), + InsightProcessorFactory: factories.ExternalEventTrack(), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) require.NoError(t, err) assert.False(t, client.IsInitialized()) - res, detail, err := client.Variation("flag", testUser1, "error") + res, detail, err := client.Variation("ff-test-string", testUser1, "error") assert.Equal(t, err, clientNotInitialized) assert.Equal(t, ReasonClientNotReady, detail.Reason) assert.Equal(t, "error", res) @@ -79,6 +112,8 @@ func TestFBClientBootStrap(t *testing.T) { assert.False(t, allState.IsSuccess()) if client.GetDataUpdateStatusProvider().WaitForOKState(200 * time.Millisecond) { assert.True(t, client.IsInitialized()) + res, _, _ = client.Variation("ff-test-string", testUser1, "error") + assert.Equal(t, "others", res) } _ = client.Close() }) @@ -86,14 +121,20 @@ func TestFBClientBootStrap(t *testing.T) { config := FBConfig{ StartWait: 50 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 100*time.Millisecond), - InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(insight2.NewMockSender(), 100, 100*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 100*time.Millisecond), + InsightProcessorFactory: factories.ExternalEventTrack(), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) assert.Equal(t, err, initializationTimeout) assert.False(t, client.IsInitialized()) + res, detail, err := client.Variation("ff-test-string", testUser1, "error") + assert.Equal(t, err, clientNotInitialized) + assert.Equal(t, ReasonClientNotReady, detail.Reason) + assert.Equal(t, "error", res) if client.GetDataUpdateStatusProvider().WaitForOKState(200 * time.Millisecond) { assert.True(t, client.IsInitialized()) + res, _, _ = client.Variation("ff-test-string", testUser1, "error") + assert.Equal(t, "others", res) } _ = client.Close() }) @@ -282,7 +323,7 @@ func TestFBTrackEvent(t *testing.T) { config := FBConfig{ StartWait: 200 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 10*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 10*time.Millisecond), InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(sender, 100, 100*time.Millisecond), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) @@ -302,7 +343,7 @@ func TestFBTrackEvent(t *testing.T) { config := FBConfig{ StartWait: 200 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 10*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 10*time.Millisecond), InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(sender, 100, 100*time.Millisecond), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) @@ -324,7 +365,7 @@ func TestFBTrackEvent(t *testing.T) { config := FBConfig{ StartWait: 200 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 10*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 10*time.Millisecond), InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(sender, 100, 100*time.Millisecond), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) @@ -344,7 +385,7 @@ func TestFBTrackEvent(t *testing.T) { config := FBConfig{ StartWait: 200 * time.Millisecond, DataStorageFactory: datastorage.NewMockDataStorageBuilder(), - DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, 10*time.Millisecond), + DataSynchronizerFactory: datasynchronization.NewMockStreamingBuilder(true, true, 10*time.Millisecond), InsightProcessorFactory: insight2.NewMockInsightProcessorFactory(sender, 100, 100*time.Millisecond), } client, err := MakeCustomFBClient(fakeEnvSecret, "ws://fake-url", "http://fake-url", config) diff --git a/fbconfig.go b/fbconfig.go index cd6d1d7..8e1a15d 100644 --- a/fbconfig.go +++ b/fbconfig.go @@ -52,5 +52,5 @@ var DefaultFBConfig *FBConfig = &FBConfig{ DataStorageFactory: factories.NewInMemoryStorageBuilder(), DataSynchronizerFactory: factories.NewStreamingBuilder(), InsightProcessorFactory: factories.NewInsightProcessorBuilder(), - LogLevel: ERROR, + LogLevel: INFO, } diff --git a/internal/datasynchronization/mock_streaming.go b/internal/datasynchronization/mock_streaming.go index debb1de..42486d7 100644 --- a/internal/datasynchronization/mock_streaming.go +++ b/internal/datasynchronization/mock_streaming.go @@ -10,8 +10,10 @@ import ( type MockStreaming struct { success bool + loadData bool waitTime time.Duration realDataUpdator DataUpdater + initialized bool } func (m *MockStreaming) Close() error { @@ -19,7 +21,7 @@ func (m *MockStreaming) Close() error { } func (m *MockStreaming) IsInitialized() bool { - return m.realDataUpdator.StorageInitialized() + return m.initialized } func (m *MockStreaming) Start() <-chan struct{} { @@ -27,11 +29,14 @@ func (m *MockStreaming) Start() <-chan struct{} { go func() { time.Sleep(m.waitTime) if m.success { - jsonBytes, _ := fixtures.LoadFBClientTestData() - var all data.All - _ = json.Unmarshal(jsonBytes, &all) - m.realDataUpdator.Init(all.Data.ToStorageType(), all.Data.GetTimestamp()) - m.realDataUpdator.UpdateStatus(OKState()) + m.initialized = true + if m.loadData { + jsonBytes, _ := fixtures.LoadFBClientTestData() + var all data.All + _ = json.Unmarshal(jsonBytes, &all) + m.realDataUpdator.Init(all.Data.ToStorageType(), all.Data.GetTimestamp()) + m.realDataUpdator.UpdateStatus(OKState()) + } } close(ret) }() @@ -40,16 +45,17 @@ func (m *MockStreaming) Start() <-chan struct{} { type MockStreamingBuilder struct { success bool + loadData bool waitTime time.Duration } -func NewMockStreamingBuilder(success bool, waitTime time.Duration) *MockStreamingBuilder { - return &MockStreamingBuilder{success: success, waitTime: waitTime} +func NewMockStreamingBuilder(success bool, loadDate bool, waitTime time.Duration) *MockStreamingBuilder { + return &MockStreamingBuilder{success: success, loadData: loadDate, waitTime: waitTime} } func (m *MockStreamingBuilder) CreateDataSynchronizer(_ Context, dataUpdater DataUpdater) (DataSynchronizer, error) { if m.waitTime <= 0 { m.waitTime = 100 * time.Millisecond } - return &MockStreaming{success: true, waitTime: m.waitTime, realDataUpdator: dataUpdater}, nil + return &MockStreaming{success: m.success, loadData: m.loadData, waitTime: m.waitTime, realDataUpdator: dataUpdater}, nil } diff --git a/internal/datasynchronization/streaming.go b/internal/datasynchronization/streaming.go index d45a5a3..ab46946 100644 --- a/internal/datasynchronization/streaming.go +++ b/internal/datasynchronization/streaming.go @@ -257,11 +257,14 @@ func (s *Streaming) onDataProcess(allData *data.All) bool { } if success { s.readyOnce.Do(func() { - log.LogDebug("processing data is well done") s.initialized = true close(s.readyCh) - s.dataUpdater.UpdateStatus(OKState()) }) + // if data storage is not yet initialized, we should keep the status as INITIALIZING + if s.dataUpdater.StorageInitialized() { + log.LogDebug("processing data is well done") + s.dataUpdater.UpdateStatus(OKState()) + } } return success }