Skip to content

Commit 6c4ae6e

Browse files
author
Michael Ng
authored
refac(config): Make project config a top-level object we pass around. (#39)
1 parent 99f8802 commit 6c4ae6e

15 files changed

+231
-137
lines changed

optimizely/client/client.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package client
1919
import (
2020
"errors"
2121

22-
"github.com/optimizely/go-sdk/optimizely/config"
22+
"github.com/optimizely/go-sdk/optimizely"
2323
"github.com/optimizely/go-sdk/optimizely/decision"
2424
"github.com/optimizely/go-sdk/optimizely/entities"
2525
"github.com/optimizely/go-sdk/optimizely/logging"
@@ -29,7 +29,7 @@ var logger = logging.GetLogger("Client")
2929

3030
// OptimizelyClient is the entry point to the Optimizely SDK
3131
type OptimizelyClient struct {
32-
configManager config.ProjectConfigManager
32+
configManager optimizely.ProjectConfigManager
3333
decisionService decision.DecisionService
3434
isValid bool
3535
}
@@ -44,14 +44,9 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit
4444
}
4545

4646
projectConfig := o.configManager.GetConfig()
47-
feature, err := projectConfig.GetFeatureByKey(featureKey)
48-
if err != nil {
49-
return false, err
50-
}
51-
52-
// @TODO(mng): Include assigned group for mutex support
5347
featureDecisionContext := decision.FeatureDecisionContext{
54-
Feature: feature,
48+
FeatureKey: featureKey,
49+
ProjectConfig: projectConfig,
5550
}
5651

5752
featureDecision, err := o.decisionService.GetFeatureDecision(featureDecisionContext, userContext)
@@ -61,5 +56,5 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit
6156
}
6257

6358
// @TODO(mng): send impression event
64-
return featureDecision.FeatureEnabled, nil
59+
return featureDecision.Variation.FeatureEnabled, nil
6560
}

optimizely/client/client_test.go

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import (
2222

2323
"github.com/stretchr/testify/assert"
2424

25-
"github.com/optimizely/go-sdk/optimizely/config"
25+
"github.com/optimizely/go-sdk/optimizely"
2626
"github.com/optimizely/go-sdk/optimizely/decision"
2727
"github.com/optimizely/go-sdk/optimizely/entities"
2828
"github.com/stretchr/testify/mock"
2929
)
3030

3131
type MockProjectConfig struct {
32-
config.ProjectConfig
32+
optimizely.ProjectConfig
3333
mock.Mock
3434
}
3535

@@ -42,9 +42,9 @@ type MockProjectConfigManager struct {
4242
mock.Mock
4343
}
4444

45-
func (p *MockProjectConfigManager) GetConfig() config.ProjectConfig {
45+
func (p *MockProjectConfigManager) GetConfig() optimizely.ProjectConfig {
4646
args := p.Called()
47-
return args.Get(0).(config.ProjectConfig)
47+
return args.Get(0).(optimizely.ProjectConfig)
4848
}
4949

5050
type MockDecisionService struct {
@@ -58,37 +58,31 @@ func (m *MockDecisionService) GetFeatureDecision(decisionContext decision.Featur
5858

5959
func TestIsFeatureEnabled(t *testing.T) {
6060
testUserContext := entities.UserContext{ID: "test_user_1"}
61-
testFeatureKey := "test_feature_key"
62-
testFeature := entities.Feature{
63-
Key: testFeatureKey,
64-
FeatureExperiments: []entities.Experiment{
65-
entities.Experiment{
66-
ID: "111111",
67-
Variations: map[string]entities.Variation{
68-
"22222": entities.Variation{
69-
ID: "22222",
70-
Key: "22222",
71-
FeatureEnabled: true,
72-
},
73-
},
74-
},
75-
},
61+
testVariation := entities.Variation{
62+
ID: "22222",
63+
Key: "22222",
64+
FeatureEnabled: true,
7665
}
66+
testExperiment := entities.Experiment{
67+
ID: "111111",
68+
Variations: map[string]entities.Variation{"22222": testVariation},
69+
}
70+
testFeatureKey := "test_feature_key"
7771

7872
// Test happy path
7973
mockConfig := new(MockProjectConfig)
80-
mockConfig.On("GetFeatureByKey", testFeatureKey).Return(testFeature, nil)
81-
8274
mockConfigManager := new(MockProjectConfigManager)
8375
mockConfigManager.On("GetConfig").Return(mockConfig)
8476

8577
// Set up the mock decision service and its return value
8678
testDecisionContext := decision.FeatureDecisionContext{
87-
Feature: testFeature,
79+
FeatureKey: testFeatureKey,
80+
ProjectConfig: mockConfig,
8881
}
8982

9083
expectedFeatureDecision := decision.FeatureDecision{
91-
FeatureEnabled: true,
84+
Experiment: testExperiment,
85+
Variation: testVariation,
9286
Decision: decision.Decision{
9387
DecisionMade: true,
9488
},
@@ -127,15 +121,19 @@ func TestIsFeatureEnabledErrorCases(t *testing.T) {
127121
mockConfigManager.AssertNotCalled(t, "GetFeatureByKey")
128122
mockDecisionService.AssertNotCalled(t, "GetFeatureDecision")
129123

130-
// Test invalid feature key
124+
// Test decision serviceinvalid feature key
131125
expectedError := errors.New("Invalid feature key")
132126
mockConfig := new(MockProjectConfig)
133-
mockConfig.On("GetFeatureByKey", testFeatureKey).Return(entities.Feature{}, expectedError)
134127

135128
mockConfigManager = new(MockProjectConfigManager)
136129
mockConfigManager.On("GetConfig").Return(mockConfig)
137-
mockDecisionService = new(MockDecisionService)
138130

131+
testFeatureDecisionContext := decision.FeatureDecisionContext{
132+
FeatureKey: testFeatureKey,
133+
ProjectConfig: mockConfig,
134+
}
135+
mockDecisionService = new(MockDecisionService)
136+
mockDecisionService.On("GetFeatureDecision", testFeatureDecisionContext, testUserContext).Return(decision.FeatureDecision{}, expectedError)
139137
client = OptimizelyClient{
140138
configManager: mockConfigManager,
141139
decisionService: mockDecisionService,
@@ -146,4 +144,6 @@ func TestIsFeatureEnabledErrorCases(t *testing.T) {
146144
assert.Equal(t, expectedError, err)
147145
}
148146
assert.False(t, result)
147+
mockConfigManager.AssertExpectations(t)
148+
mockDecisionService.AssertExpectations(t)
149149
}

optimizely/client/factory.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package client
1818

1919
import (
20+
"github.com/optimizely/go-sdk/optimizely"
2021
"github.com/optimizely/go-sdk/optimizely/config"
2122
"github.com/optimizely/go-sdk/optimizely/config/datafileProjectConfig"
2223
"github.com/optimizely/go-sdk/optimizely/decision"
@@ -30,8 +31,8 @@ type OptimizelyFactory struct {
3031

3132
// Client returns a client initialized with the defaults
3233
func (f OptimizelyFactory) Client() OptimizelyClient {
33-
var projectConfig config.ProjectConfig
34-
var configManager config.ProjectConfigManager
34+
var projectConfig optimizely.ProjectConfig
35+
var configManager optimizely.ProjectConfigManager
3536
if f.Datafile != nil {
3637
projectConfig = datafileProjectConfig.NewDatafileProjectConfig(f.Datafile)
3738

optimizely/config/datafileProjectConfig/config.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,42 +29,42 @@ var logger = logging.GetLogger("DatafileProjectConfig")
2929

3030
// DatafileProjectConfig is a project config backed by a datafile
3131
type DatafileProjectConfig struct {
32+
accountID string
33+
anonymizeIP bool
34+
attributeKeyToIDMap map[string]string
3235
audienceMap map[string]entities.Audience
33-
experimentMap map[string]entities.Experiment
36+
botFiltering bool
37+
eventMap map[string]entities.Event
3438
experimentKeyToIDMap map[string]string
39+
experimentMap map[string]entities.Experiment
3540
featureMap map[string]entities.Feature
36-
attributeKeyToIDMap map[string]string
37-
eventMap map[string]entities.Event
38-
projectID string
39-
revision string
40-
accountID string
41-
anonymizeIP bool
42-
botFiltering bool
43-
41+
groupMap map[string]entities.Group
42+
projectID string
43+
revision string
4444
}
4545

46-
func (config DatafileProjectConfig) GetProjectID() string {
47-
return config.projectID
46+
func (c DatafileProjectConfig) GetProjectID() string {
47+
return c.projectID
4848
}
4949

50-
func (config DatafileProjectConfig) GetRevision() string {
51-
return config.revision
50+
func (c DatafileProjectConfig) GetRevision() string {
51+
return c.revision
5252
}
5353

54-
func (config DatafileProjectConfig) GetAccountID() string {
55-
return config.accountID
54+
func (c DatafileProjectConfig) GetAccountID() string {
55+
return c.accountID
5656
}
5757

58-
func (config DatafileProjectConfig) GetAnonymizeIP() bool {
59-
return config.anonymizeIP
58+
func (c DatafileProjectConfig) GetAnonymizeIP() bool {
59+
return c.anonymizeIP
6060
}
6161

62-
func (config DatafileProjectConfig) GetAttributeID(key string) string {
63-
return config.attributeKeyToIDMap[key]
62+
func (c DatafileProjectConfig) GetAttributeID(key string) string {
63+
return c.attributeKeyToIDMap[key]
6464
}
6565

66-
func (config DatafileProjectConfig) GetBotFiltering() bool {
67-
return config.botFiltering
66+
func (c DatafileProjectConfig) GetBotFiltering() bool {
67+
return c.botFiltering
6868
}
6969

7070
// NewDatafileProjectConfig initializes a new datafile from a json byte array using the default JSON datafile parser
@@ -86,8 +86,8 @@ func NewDatafileProjectConfig(jsonDatafile []byte) *DatafileProjectConfig {
8686
}
8787

8888
// GetEventByKey returns the event with the given key
89-
func (config DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
90-
if event, ok := config.eventMap[eventKey]; ok {
89+
func (c DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
90+
if event, ok := c.eventMap[eventKey]; ok {
9191
return event, nil
9292
}
9393

@@ -96,11 +96,42 @@ func (config DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Eve
9696
}
9797

9898
// GetFeatureByKey returns the feature with the given key
99-
func (config DatafileProjectConfig) GetFeatureByKey(featureKey string) (entities.Feature, error) {
100-
if feature, ok := config.featureMap[featureKey]; ok {
99+
func (c DatafileProjectConfig) GetFeatureByKey(featureKey string) (entities.Feature, error) {
100+
if feature, ok := c.featureMap[featureKey]; ok {
101101
return feature, nil
102102
}
103103

104104
errMessage := fmt.Sprintf("Feature with key %s not found", featureKey)
105105
return entities.Feature{}, errors.New(errMessage)
106106
}
107+
108+
// GetAudienceByID returns the audience with the given ID
109+
func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audience, error) {
110+
if audience, ok := c.audienceMap[audienceID]; ok {
111+
return audience, nil
112+
}
113+
114+
errMessage := fmt.Sprintf(`Audience with ID "%s" not found`, audienceID)
115+
return entities.Audience{}, errors.New(errMessage)
116+
}
117+
118+
// GetExperimentByKey returns the experiment with the given key
119+
func (c DatafileProjectConfig) GetExperimentByKey(experimentKey string) (entities.Experiment, error) {
120+
if experimentID, ok := c.experimentKeyToIDMap[experimentKey]; ok {
121+
experiment := c.experimentMap[experimentID]
122+
return experiment, nil
123+
}
124+
125+
errMessage := fmt.Sprintf(`Experiment with key "%s" not found`, experimentKey)
126+
return entities.Experiment{}, errors.New(errMessage)
127+
}
128+
129+
// GetGroupByID returns the group with the given ID
130+
func (c DatafileProjectConfig) GetGroupByID(groupID string) (entities.Group, error) {
131+
if group, ok := c.groupMap[groupID]; ok {
132+
return group, nil
133+
}
134+
135+
errMessage := fmt.Sprintf(`Group with ID "%s" not found`, groupID)
136+
return entities.Group{}, errors.New(errMessage)
137+
}

optimizely/config/static_manager.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,27 @@
1616

1717
package config
1818

19-
import "sync"
19+
import (
20+
"sync"
21+
22+
"github.com/optimizely/go-sdk/optimizely"
23+
)
2024

2125
// StaticProjectConfigManager maintains a static copy of the project config
2226
type StaticProjectConfigManager struct {
23-
projectConfig ProjectConfig
27+
projectConfig optimizely.ProjectConfig
2428
configLock sync.Mutex
2529
}
2630

2731
// NewStaticProjectConfigManager creates a new instance of the manager with the given project config
28-
func NewStaticProjectConfigManager(config ProjectConfig) *StaticProjectConfigManager {
32+
func NewStaticProjectConfigManager(config optimizely.ProjectConfig) *StaticProjectConfigManager {
2933
return &StaticProjectConfigManager{
3034
projectConfig: config,
3135
}
3236
}
3337

3438
// GetConfig returns the project config
35-
func (cm *StaticProjectConfigManager) GetConfig() ProjectConfig {
39+
func (cm *StaticProjectConfigManager) GetConfig() optimizely.ProjectConfig {
3640
cm.configLock.Lock()
3741
defer cm.configLock.Unlock()
3842
return cm.projectConfig

optimizely/decision/composite_experiment_service.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,18 @@ func NewCompositeExperimentService() *CompositeExperimentService {
4242

4343
// GetDecision returns a decision for the given experiment and user context
4444
func (s CompositeExperimentService) GetDecision(decisionContext ExperimentDecisionContext, userContext entities.UserContext) (ExperimentDecision, error) {
45+
_, err := decisionContext.ProjectConfig.GetExperimentByKey(decisionContext.ExperimentKey)
46+
if err != nil {
47+
return ExperimentDecision{}, err
48+
}
49+
4550
for _, experimentService := range s.experimentDecisionServices {
4651
decision, err := experimentService.GetDecision(decisionContext, userContext)
4752
if decision.DecisionMade == true {
4853
return decision, err
4954
}
5055
}
5156

52-
experimentDecision := ExperimentDecision{
53-
Decision: Decision{
54-
DecisionMade: false,
55-
},
56-
}
57-
return experimentDecision, nil
57+
// zero-value for DecisionMade is false
58+
return ExperimentDecision{}, nil
5859
}

optimizely/decision/composite_experiment_service_test.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,30 @@ func (m *MockExperimentDecisionService) GetDecision(decisionContext ExperimentDe
3535
}
3636

3737
func TestCompositeExperimentServiceGetDecision(t *testing.T) {
38-
testDecisionContext := ExperimentDecisionContext{
39-
Experiment: entities.Experiment{
40-
ID: "111111",
41-
Variations: map[string]entities.Variation{
42-
"22222": entities.Variation{
43-
ID: "22222",
44-
Key: "22222",
45-
},
38+
testExperimentKey := "test_experiment"
39+
testExperiment := entities.Experiment{
40+
ID: "111111",
41+
Key: testExperimentKey,
42+
Variations: map[string]entities.Variation{
43+
"22222": entities.Variation{
44+
ID: "22222",
45+
Key: "22222",
4646
},
4747
},
4848
}
49+
mockProjectConfig := new(MockProjectConfig)
50+
mockProjectConfig.On("GetExperimentByKey", testExperimentKey).Return(testExperiment, nil)
51+
testDecisionContext := ExperimentDecisionContext{
52+
ExperimentKey: testExperimentKey,
53+
ProjectConfig: mockProjectConfig,
54+
}
4955

5056
testUserContext := entities.UserContext{
5157
ID: "test_user_1",
5258
}
5359

5460
expectedExperimentDecision := ExperimentDecision{
55-
Variation: testDecisionContext.Experiment.Variations["22222"],
61+
Variation: testExperiment.Variations["22222"],
5662
Decision: Decision{
5763
DecisionMade: true,
5864
},
@@ -86,7 +92,7 @@ func TestCompositeExperimentServiceGetDecision(t *testing.T) {
8692

8793
mockExperimentDecisionService2 = new(MockExperimentDecisionService)
8894
expectedExperimentDecision2 := ExperimentDecision{
89-
Variation: testDecisionContext.Experiment.Variations["22222"],
95+
Variation: testExperiment.Variations["22222"],
9096
Decision: Decision{
9197
DecisionMade: true,
9298
},

0 commit comments

Comments
 (0)