Skip to content

Commit 9bc5b0b

Browse files
author
Michael Ng
authored
refac(context): Refactor to make execution context optional for components (#130)
1 parent 6601ecc commit 9bc5b0b

File tree

11 files changed

+179
-116
lines changed

11 files changed

+179
-116
lines changed

examples/benchmark/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func stressTest() {
4242
// Creates a default, canceleable context
4343
decisionService := decision.NewCompositeService("sdk_key")
4444

45-
clientApp, err := optlyClient.Client(client.DecisionService(decisionService))
45+
clientApp, err := optlyClient.Client(client.WithDecisionService(decisionService))
4646
if err != nil {
4747
log.Print(err)
4848
}

examples/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ func main() {
6262
/************* Setting Polling Interval ********************/
6363

6464
optimizelyClient, _ = optimizelyFactory.Client(
65-
client.PollingConfigManager(sdkKey, time.Second, nil),
66-
client.CompositeDecisionService(sdkKey),
67-
client.BatchEventProcessor(event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval),
65+
client.WithPollingConfigManager(sdkKey, time.Second, nil),
66+
client.WithCompositeDecisionService(sdkKey),
67+
client.WithBatchEventProcessor(event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval),
6868
)
6969
optimizelyClient.Close()
7070
}

optimizely/client/client_test.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,12 @@ import (
2424
"github.com/optimizely/go-sdk/optimizely/decision"
2525
"github.com/optimizely/go-sdk/optimizely/entities"
2626
"github.com/optimizely/go-sdk/optimizely/event"
27-
"github.com/optimizely/go-sdk/optimizely/notification"
2827

2928
"github.com/stretchr/testify/assert"
3029
"github.com/stretchr/testify/mock"
3130
"github.com/stretchr/testify/suite"
3231
)
3332

34-
type MockProjectConfigManager struct {
35-
projectConfig optimizely.ProjectConfig
36-
mock.Mock
37-
}
38-
39-
func (p *MockProjectConfigManager) GetConfig() (optimizely.ProjectConfig, error) {
40-
if p.projectConfig != nil {
41-
return p.projectConfig, nil
42-
}
43-
44-
args := p.Called()
45-
return args.Get(0).(optimizely.ProjectConfig), args.Error(1)
46-
}
47-
48-
func (p *MockProjectConfigManager) OnProjectConfigUpdate(callback func(notification.ProjectConfigUpdateNotification)) (int, error) {
49-
return 0, nil
50-
}
51-
52-
func (p *MockProjectConfigManager) RemoveOnProjectConfigUpdate(id int) error {
53-
return nil
54-
}
55-
5633
func ValidProjectConfigManager() *MockProjectConfigManager {
5734
p := new(MockProjectConfigManager)
5835
p.projectConfig = new(TestConfig)

optimizely/client/factory.go

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type OptimizelyFactory struct {
3535
}
3636

3737
// OptionFunc is a type to a proper func
38-
type OptionFunc func(*OptimizelyClient, utils.ExecutionCtx)
38+
type OptionFunc func(*OptimizelyClient)
3939

4040
// Client gets client and sets some parameters
4141
func (f OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClient, error) {
@@ -45,79 +45,94 @@ func (f OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClien
4545
appClient := &OptimizelyClient{
4646
executionCtx: executionCtx,
4747
DecisionService: decision.NewCompositeService(f.SDKKey),
48-
EventProcessor: event.NewEventProcessor(executionCtx, event.BatchSize(event.DefaultBatchSize),
48+
EventProcessor: event.NewEventProcessor(event.BatchSize(event.DefaultBatchSize),
4949
event.QueueSize(event.DefaultEventQueueSize), event.FlushInterval(event.DefaultEventFlushInterval)),
5050
}
5151

5252
for _, opt := range clientOptions {
53-
opt(appClient, executionCtx)
53+
opt(appClient)
5454
}
5555

5656
if f.SDKKey == "" && f.Datafile == nil && appClient.ConfigManager == nil {
5757
return nil, errors.New("unable to instantiate client: no project config manager, SDK key, or a Datafile provided")
5858
}
5959

6060
if appClient.ConfigManager == nil { // if it was not passed then assign here
61-
62-
appClient.ConfigManager = config.NewPollingProjectConfigManager(executionCtx, f.SDKKey,
61+
appClient.ConfigManager = config.NewPollingProjectConfigManager(f.SDKKey,
6362
config.InitialDatafile(f.Datafile), config.PollingInterval(config.DefaultPollingInterval))
6463
}
6564

65+
// Initialize the default services with the execution context
66+
if pollingConfigManager, ok := appClient.ConfigManager.(*config.PollingProjectConfigManager); ok {
67+
pollingConfigManager.Start(appClient.executionCtx)
68+
}
69+
70+
if queueingProcessor, ok := appClient.EventProcessor.(*event.QueueingEventProcessor); ok {
71+
queueingProcessor.Start(appClient.executionCtx)
72+
}
73+
6674
return appClient, nil
6775
}
6876

69-
// PollingConfigManager sets polling config manager on a client
70-
func PollingConfigManager(sdkKey string, pollingInterval time.Duration, initDataFile []byte) OptionFunc {
71-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
72-
f.ConfigManager = config.NewPollingProjectConfigManager(f.executionCtx, sdkKey, config.InitialDatafile(initDataFile),
77+
// WithPollingConfigManager sets polling config manager on a client
78+
func WithPollingConfigManager(sdkKey string, pollingInterval time.Duration, initDataFile []byte) OptionFunc {
79+
return func(f *OptimizelyClient) {
80+
f.ConfigManager = config.NewPollingProjectConfigManager(sdkKey, config.InitialDatafile(initDataFile),
7381
config.PollingInterval(pollingInterval))
7482
}
7583
}
7684

77-
// PollingConfigManagerRequester sets polling config manager on a client
78-
func PollingConfigManagerRequester(requester utils.Requester, pollingInterval time.Duration, initDataFile []byte) OptionFunc {
79-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
80-
f.ConfigManager = config.NewPollingProjectConfigManager(f.executionCtx, "", config.InitialDatafile(initDataFile),
85+
// WithPollingConfigManagerRequester sets polling config manager on a client
86+
func WithPollingConfigManagerRequester(requester utils.Requester, pollingInterval time.Duration, initDataFile []byte) OptionFunc {
87+
return func(f *OptimizelyClient) {
88+
f.ConfigManager = config.NewPollingProjectConfigManager("", config.InitialDatafile(initDataFile),
8189
config.PollingInterval(pollingInterval), config.Requester(requester))
8290
}
8391
}
8492

85-
// ConfigManager sets polling config manager on a client
86-
func ConfigManager(configManager optimizely.ProjectConfigManager) OptionFunc {
87-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
93+
// WithConfigManager sets polling config manager on a client
94+
func WithConfigManager(configManager optimizely.ProjectConfigManager) OptionFunc {
95+
return func(f *OptimizelyClient) {
8896
f.ConfigManager = configManager
8997
}
9098
}
9199

92-
// CompositeDecisionService sets decision service on a client
93-
func CompositeDecisionService(sdkKey string) OptionFunc {
94-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
100+
// WithCompositeDecisionService sets decision service on a client
101+
func WithCompositeDecisionService(sdkKey string) OptionFunc {
102+
return func(f *OptimizelyClient) {
95103
f.DecisionService = decision.NewCompositeService(sdkKey)
96104
}
97105
}
98106

99-
// DecisionService sets decision service on a client
100-
func DecisionService(decisionService decision.Service) OptionFunc {
101-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
107+
// WithDecisionService sets decision service on a client
108+
func WithDecisionService(decisionService decision.Service) OptionFunc {
109+
return func(f *OptimizelyClient) {
102110
f.DecisionService = decisionService
103111
}
104112
}
105113

106-
// BatchEventProcessor sets event processor on a client
107-
func BatchEventProcessor(batchSize, queueSize int, flushInterval time.Duration) OptionFunc {
108-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
109-
f.EventProcessor = event.NewEventProcessor(executionCtx, event.BatchSize(batchSize),
114+
// WithBatchEventProcessor sets event processor on a client
115+
func WithBatchEventProcessor(batchSize, queueSize int, flushInterval time.Duration) OptionFunc {
116+
return func(f *OptimizelyClient) {
117+
f.EventProcessor = event.NewEventProcessor(event.BatchSize(batchSize),
110118
event.QueueSize(queueSize), event.FlushInterval(flushInterval))
111119
}
112120
}
113121

114-
// EventProcessor sets event processor on a client
115-
func EventProcessor(eventProcessor event.Processor) OptionFunc {
116-
return func(f *OptimizelyClient, executionCtx utils.ExecutionCtx) {
122+
// WithEventProcessor sets event processor on a client
123+
func WithEventProcessor(eventProcessor event.Processor) OptionFunc {
124+
return func(f *OptimizelyClient) {
117125
f.EventProcessor = eventProcessor
118126
}
119127
}
120128

129+
// WithExecutionContext allows user to pass in their own execution context to override the default one in the client
130+
func WithExecutionContext(executionContext utils.ExecutionCtx) OptionFunc {
131+
return func(f *OptimizelyClient) {
132+
f.executionCtx = executionContext
133+
}
134+
}
135+
121136
// StaticClient returns a client initialized with a static project config
122137
func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
123138
var configManager optimizely.ProjectConfigManager
@@ -142,9 +157,9 @@ func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
142157
}
143158

144159
optlyClient, e := f.Client(
145-
ConfigManager(configManager),
146-
CompositeDecisionService(f.SDKKey),
147-
BatchEventProcessor(event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval),
160+
WithConfigManager(configManager),
161+
WithCompositeDecisionService(f.SDKKey),
162+
WithBatchEventProcessor(event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval),
148163
)
149164
return optlyClient, e
150165
}

optimizely/client/factory_test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/optimizely/go-sdk/optimizely/config"
2424
"github.com/optimizely/go-sdk/optimizely/config/datafileprojectconfig"
2525
"github.com/optimizely/go-sdk/optimizely/event"
26+
"github.com/optimizely/go-sdk/optimizely/utils"
2627

2728
"github.com/stretchr/testify/assert"
2829
)
@@ -61,7 +62,7 @@ func TestClientWithProjectConfigManagerInOptions(t *testing.T) {
6162
projectConfig := datafileprojectconfig.DatafileProjectConfig{}
6263
configManager := config.NewStaticProjectConfigManager(projectConfig)
6364

64-
optimizelyClient, err := factory.Client(ConfigManager(configManager))
65+
optimizelyClient, err := factory.Client(WithConfigManager(configManager))
6566
assert.NoError(t, err)
6667
assert.NotNil(t, optimizelyClient.ConfigManager)
6768
assert.NotNil(t, optimizelyClient.DecisionService)
@@ -80,8 +81,29 @@ func TestClientWithDecisionServiceAndEventProcessorInOptions(t *testing.T) {
8081
EventDispatcher: &MockDispatcher{},
8182
}
8283

83-
optimizelyClient, err := factory.Client(ConfigManager(configManager), DecisionService(decisionService), EventProcessor(processor))
84+
optimizelyClient, err := factory.Client(WithConfigManager(configManager), WithDecisionService(decisionService), WithEventProcessor(processor))
8485
assert.NoError(t, err)
8586
assert.Equal(t, decisionService, optimizelyClient.DecisionService)
8687
assert.Equal(t, processor, optimizelyClient.EventProcessor)
8788
}
89+
90+
func TestClientWithCustomCtx(t *testing.T) {
91+
factory := OptimizelyFactory{}
92+
testExecutionCtx := utils.NewCancelableExecutionCtx()
93+
mockConfigManager := new(MockProjectConfigManager)
94+
client, err := factory.Client(
95+
WithConfigManager(mockConfigManager),
96+
WithExecutionContext(testExecutionCtx),
97+
)
98+
assert.NoError(t, err)
99+
assert.Equal(t, client.executionCtx, testExecutionCtx)
100+
}
101+
102+
func TestStaticClient(t *testing.T) {
103+
factory := OptimizelyFactory{Datafile: []byte(`{"revision": "42"}`)}
104+
optlyClient, err := factory.StaticClient()
105+
assert.NoError(t, err)
106+
107+
parsedConfig, _ := optlyClient.ConfigManager.GetConfig()
108+
assert.Equal(t, "42", parsedConfig.GetRevision())
109+
}

optimizely/client/fixtures_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ func (c *MockProjectConfig) GetBotFiltering() bool {
7878
return false
7979
}
8080

81+
type MockProjectConfigManager struct {
82+
projectConfig optimizely.ProjectConfig
83+
mock.Mock
84+
}
85+
86+
func (p *MockProjectConfigManager) GetConfig() (optimizely.ProjectConfig, error) {
87+
if p.projectConfig != nil {
88+
return p.projectConfig, nil
89+
}
90+
91+
args := p.Called()
92+
return args.Get(0).(optimizely.ProjectConfig), args.Error(1)
93+
}
94+
95+
func (p *MockProjectConfigManager) OnProjectConfigUpdate(callback func(notification.ProjectConfigUpdateNotification)) (int, error) {
96+
return 0, nil
97+
}
98+
99+
func (p *MockProjectConfigManager) RemoveOnProjectConfigUpdate(id int) error {
100+
return nil
101+
}
102+
81103
type MockDecisionService struct {
82104
decision.Service
83105
mock.Mock

optimizely/config/polling_manager.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type PollingProjectConfigManager struct {
4848
configLock sync.RWMutex
4949
err error
5050
projectConfig optimizely.ProjectConfig
51-
exeCtx utils.ExecutionCtx // context used for execution control
5251
}
5352

5453
// OptionFunc is a type to a proper func
@@ -132,26 +131,28 @@ func (cm *PollingProjectConfigManager) SyncConfig(datafile []byte) {
132131
}
133132
}
134133

135-
func (cm *PollingProjectConfigManager) start() {
136-
137-
t := time.NewTicker(cm.pollingInterval)
138-
for {
139-
select {
140-
case <-t.C:
141-
cm.SyncConfig([]byte{})
142-
case <-cm.exeCtx.GetContext().Done():
143-
cmLogger.Debug("Polling Config Manager Stopped")
144-
return
134+
// Start starts the polling
135+
func (cm *PollingProjectConfigManager) Start(exeCtx utils.ExecutionCtx) {
136+
go func() {
137+
cmLogger.Debug("Polling Config Manager Initiated")
138+
t := time.NewTicker(cm.pollingInterval)
139+
for {
140+
select {
141+
case <-t.C:
142+
cm.SyncConfig([]byte{})
143+
case <-exeCtx.GetContext().Done():
144+
cmLogger.Debug("Polling Config Manager Stopped")
145+
return
146+
}
145147
}
146-
}
148+
}()
147149
}
148150

149151
// NewPollingProjectConfigManager returns an instance of the polling config manager with the customized configuration
150-
func NewPollingProjectConfigManager(exeCtx utils.ExecutionCtx, sdkKey string, pollingMangerOptions ...OptionFunc) *PollingProjectConfigManager {
152+
func NewPollingProjectConfigManager(sdkKey string, pollingMangerOptions ...OptionFunc) *PollingProjectConfigManager {
151153
url := fmt.Sprintf(DatafileURLTemplate, sdkKey)
152154

153155
pollingProjectConfigManager := PollingProjectConfigManager{
154-
exeCtx: exeCtx,
155156
notificationCenter: registry.GetNotificationCenter(sdkKey),
156157
pollingInterval: DefaultPollingInterval,
157158
requester: utils.NewHTTPRequester(url),
@@ -163,9 +164,6 @@ func NewPollingProjectConfigManager(exeCtx utils.ExecutionCtx, sdkKey string, po
163164

164165
initDatafile := pollingProjectConfigManager.initDatafile
165166
pollingProjectConfigManager.SyncConfig(initDatafile) // initial poll
166-
167-
cmLogger.Debug("Polling Config Manager Initiated")
168-
go pollingProjectConfigManager.start()
169167
return &pollingProjectConfigManager
170168
}
171169

0 commit comments

Comments
 (0)