Skip to content

Commit e5fe7fb

Browse files
pawels-optimizelyMichael Ng
authored andcommitted
feat: Add simple client factory method and remove metrics logic (#52)
1 parent 368d5e5 commit e5fe7fb

File tree

7 files changed

+125
-130
lines changed

7 files changed

+125
-130
lines changed

examples/main.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,20 @@ func main() {
4949
time.Sleep(1000 * time.Millisecond)
5050
fmt.Println("\nending")
5151
}
52+
fmt.Println()
5253

53-
/************* ClientWithContext ********************/
54+
/************* ClientWithOptions - custom context ********************/
5455

5556
optimizelyFactory = &client.OptimizelyFactory{
5657
SDKKey: "4SLpaJA1r1pgE6T2CoMs9q",
5758
}
5859
ctx := context.Background()
5960
ctx, cancelManager := context.WithCancel(ctx) // user can set up any context
60-
app, err = optimizelyFactory.ClientWithContext(ctx)
61+
clientOptions := client.Options{
62+
Context: ctx,
63+
}
64+
65+
app, err = optimizelyFactory.ClientWithOptions(clientOptions)
6166
cancelManager() // user can cancel anytime
6267

6368
if err != nil {
@@ -68,4 +73,23 @@ func main() {
6873
enabled, _ = app.IsFeatureEnabled("mutext_feat", user)
6974
fmt.Printf("Is feature enabled? %v\n", enabled)
7075

76+
time.Sleep(1000 * time.Millisecond)
77+
fmt.Println()
78+
79+
/************* Client ********************/
80+
81+
optimizelyFactory = &client.OptimizelyFactory{
82+
SDKKey: "4SLpaJA1r1pgE6T2CoMs9q",
83+
}
84+
85+
app, err = optimizelyFactory.Client()
86+
app.Close() // user can cancel anytime
87+
88+
if err != nil {
89+
fmt.Printf("Error instantiating client: %s", err)
90+
return
91+
}
92+
93+
enabled, _ = app.IsFeatureEnabled("mutext_feat", user)
94+
fmt.Printf("Is feature enabled? %v\n", enabled)
7195
}

optimizely/client/client.go

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

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
2223
"reflect"
@@ -35,6 +36,8 @@ type OptimizelyClient struct {
3536
configManager optimizely.ProjectConfigManager
3637
decisionService decision.DecisionService
3738
isValid bool
39+
40+
cancelFunc context.CancelFunc
3841
}
3942

4043
// IsFeatureEnabled returns true if the feature is enabled for the given user
@@ -89,3 +92,8 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit
8992
// @TODO(mng): send impression event
9093
return result, nil
9194
}
95+
96+
// Close closes the Optimizely instance and stops any ongoing tasks from its children components
97+
func (o *OptimizelyClient) Close() {
98+
o.cancelFunc()
99+
}

optimizely/client/factory.go

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,34 @@ package client
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223

2324
"github.com/optimizely/go-sdk/optimizely"
2425
"github.com/optimizely/go-sdk/optimizely/config"
2526
"github.com/optimizely/go-sdk/optimizely/decision"
2627
)
2728

29+
const datafileURLTemplate = "https://cdn.optimizely.com/datafiles/%s.json"
30+
31+
// Options are used to create an instance of the OptimizelyClient with custom configuration
32+
type Options struct {
33+
Context context.Context
34+
ProjectConfigManager optimizely.ProjectConfigManager
35+
}
36+
2837
// OptimizelyFactory is used to construct an instance of the OptimizelyClient
2938
type OptimizelyFactory struct {
3039
SDKKey string
3140
Datafile []byte
3241
}
3342

34-
// StaticClient returns a client initialized with the defaults
43+
// StaticClient returns a client initialized with a static project config
3544
func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
3645
var configManager optimizely.ProjectConfigManager
3746

3847
if f.SDKKey != "" {
39-
url := fmt.Sprintf("https://cdn.optimizely.com/datafiles/%s.json", f.SDKKey)
48+
url := fmt.Sprintf(datafileURLTemplate, f.SDKKey)
4049
staticConfigManager, err := config.NewStaticProjectConfigManagerFromURL(url)
4150

4251
if err != nil {
@@ -55,33 +64,53 @@ func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
5564
configManager = staticConfigManager
5665
}
5766

58-
decisionService := decision.NewCompositeService()
59-
client := OptimizelyClient{
60-
decisionService: decisionService,
61-
configManager: configManager,
62-
isValid: true,
67+
clientOptions := Options{
68+
ProjectConfigManager: configManager,
6369
}
64-
return &client, nil
70+
client, err := f.ClientWithOptions(clientOptions)
71+
return client, err
6572
}
6673

67-
// ClientWithContext returns a client initialized with the defaults
68-
func (f OptimizelyFactory) ClientWithContext(ctx context.Context) (*OptimizelyClient, error) {
69-
var configManager optimizely.ProjectConfigManager
70-
71-
if f.SDKKey != "" {
72-
url := fmt.Sprintf("https://cdn.optimizely.com/datafiles/%s.json", f.SDKKey)
73-
request := config.NewRequester(url)
74+
// ClientWithOptions returns a client initialized with the given configuration options
75+
func (f OptimizelyFactory) ClientWithOptions(clientOptions Options) (*OptimizelyClient, error) {
76+
client := &OptimizelyClient{
77+
isValid: false,
78+
}
7479

75-
configManager = config.NewPollingProjectConfigManager(ctx, request, f.Datafile, 0)
80+
var ctx context.Context
81+
if clientOptions.Context != nil {
82+
ctx = clientOptions.Context
83+
} else {
84+
// if no context is provided, we create our own cancellable context and hand it over to the client so the client can shut down its child processes
85+
ctx = context.Background()
86+
var cancel context.CancelFunc
87+
ctx, cancel = context.WithCancel(ctx)
88+
client.cancelFunc = cancel
89+
}
7690

77-
decisionService := decision.NewCompositeService()
78-
client := OptimizelyClient{
79-
decisionService: decisionService,
80-
configManager: configManager,
81-
isValid: true,
82-
}
83-
return &client, nil
91+
if clientOptions.ProjectConfigManager != nil {
92+
client.configManager = clientOptions.ProjectConfigManager
93+
} else if f.SDKKey != "" {
94+
url := fmt.Sprintf(datafileURLTemplate, f.SDKKey)
95+
request := config.NewRequester(url)
96+
client.configManager = config.NewPollingProjectConfigManager(ctx, request, f.Datafile, 0)
97+
} else if f.Datafile != nil {
98+
staticConfigManager, _ := config.NewStaticProjectConfigManagerFromPayload(f.Datafile)
99+
client.configManager = staticConfigManager
100+
} else {
101+
return client, errors.New("unable to instantiate client: no project config manager, SDK key, or a Datafile provided")
84102
}
85103

86-
return nil, fmt.Errorf("Cannot create ClientWithContext")
104+
// @TODO: allow decision service to be passed in via options
105+
client.decisionService = decision.NewCompositeService()
106+
client.isValid = true
107+
return client, nil
108+
}
109+
110+
// Client returns a client initialized with the defaults
111+
func (f OptimizelyFactory) Client() (*OptimizelyClient, error) {
112+
// Creates a default, canceleable context
113+
clientOptions := Options{}
114+
client, err := f.ClientWithOptions(clientOptions)
115+
return client, err
87116
}

optimizely/client/factory_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package client
18+
19+
import (
20+
"errors"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
func TestClientWithOptionsErrorCase(t *testing.T) {
27+
// Error when no config manager, sdk key, or datafile is provided
28+
factory := OptimizelyFactory{}
29+
clientOptions := Options{}
30+
31+
client, err := factory.ClientWithOptions(clientOptions)
32+
expectedErr := errors.New("unable to instantiate client: no project config manager, SDK key, or a Datafile provided")
33+
assert.False(t, client.isValid)
34+
if assert.Error(t, err) {
35+
assert.Equal(t, err, expectedErr)
36+
}
37+
}

optimizely/config/polling_manager.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"github.com/optimizely/go-sdk/optimizely"
2626
"github.com/optimizely/go-sdk/optimizely/config/datafileprojectconfig"
2727
"github.com/optimizely/go-sdk/optimizely/logging"
28-
"github.com/optimizely/go-sdk/optimizely/utils"
2928
)
3029

3130
const defaultPollingWait = time.Duration(5 * time.Minute) // default 5 minutes for polling wait
@@ -35,7 +34,6 @@ var cmLogger = logging.GetLogger("PollingConfigManager")
3534
// PollingProjectConfigManager maintains a dynamic copy of the project config
3635
type PollingProjectConfigManager struct {
3736
requester *Requester
38-
metrics *utils.Metrics
3937
pollingWait time.Duration
4038
projectConfig optimizely.ProjectConfig
4139
configLock sync.RWMutex
@@ -55,14 +53,12 @@ func (cm *PollingProjectConfigManager) activate(initialPayload []byte, init bool
5553
payload, code, e = cm.requester.Get()
5654

5755
if e != nil {
58-
cm.metrics.Inc("bad_http_request")
5956
cmLogger.Error(fmt.Sprintf("request returned with http code=%d", code), e)
6057
}
6158
}
6259

6360
projectConfig, err := datafileprojectconfig.NewDatafileProjectConfig(payload)
6461
if err != nil {
65-
cm.metrics.Inc("failed_project_config")
6662
cmLogger.Error("failed to create project config", err)
6763
}
6864

@@ -80,7 +76,6 @@ func (cm *PollingProjectConfigManager) activate(initialPayload []byte, init bool
8076
select {
8177
case <-t.C:
8278
update()
83-
cm.metrics.Inc("polls")
8479
case <-cm.ctx.Done():
8580
cmLogger.Debug("Polling Config Manager Stopped")
8681
return
@@ -95,7 +90,7 @@ func NewPollingProjectConfigManager(ctx context.Context, requester *Requester, i
9590
pollingWait = defaultPollingWait
9691
}
9792

98-
pollingProjectConfigManager := PollingProjectConfigManager{requester: requester, pollingWait: pollingWait, metrics: utils.NewMetrics(), ctx: ctx}
93+
pollingProjectConfigManager := PollingProjectConfigManager{requester: requester, pollingWait: pollingWait, ctx: ctx}
9994

10095
pollingProjectConfigManager.activate(initialPayload, true) // initial poll
10196

@@ -110,8 +105,3 @@ func (cm *PollingProjectConfigManager) GetConfig() optimizely.ProjectConfig {
110105
defer cm.configLock.RUnlock()
111106
return cm.projectConfig
112107
}
113-
114-
//GetMetrics returns a string of all metrics
115-
func (cm *PollingProjectConfigManager) GetMetrics() string {
116-
return cm.metrics.String()
117-
}

optimizely/config/polling_manager_test.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ package config
1818

1919
import (
2020
"context"
21-
"log"
2221
"testing"
23-
"time"
2422

2523
"github.com/optimizely/go-sdk/optimizely/config/datafileprojectconfig"
2624
"github.com/stretchr/testify/assert"
@@ -34,7 +32,6 @@ func TestNewPollingProjectConfigManager(t *testing.T) {
3432
// Bad SDK Key test
3533
configManager := NewPollingProjectConfigManager(context.Background(), request, []byte{}, 0)
3634
assert.Equal(t, projectConfig, configManager.GetConfig())
37-
assert.Equal(t, "[bad_http_request:1, failed_project_config:1]", configManager.GetMetrics())
3835

3936
// Good SDK Key test
4037
URL = "https://cdn.optimizely.com/datafiles/4SLpaJA1r1pgE6T2CoMs9q.json"
@@ -44,22 +41,4 @@ func TestNewPollingProjectConfigManager(t *testing.T) {
4441

4542
assert.Equal(t, "", newConfig.GetAccountID())
4643
assert.Equal(t, 4, len(newConfig.GetAudienceMap()))
47-
assert.Equal(t, "", configManager.GetMetrics())
48-
49-
}
50-
51-
func TestPollingMetrics(t *testing.T) {
52-
URL := "https://cdn.optimizely.com/datafiles/4SLpaJA1r1pgE6T2CoMs9q.json"
53-
request := NewRequester(URL)
54-
55-
// Good SDK Key test -- number of polling
56-
ctx := context.Background()
57-
ctx, cancel := context.WithCancel(ctx)
58-
configManager := NewPollingProjectConfigManager(ctx, request, []byte{}, 5*time.Second)
59-
time.Sleep(16 * time.Second)
60-
cancel()
61-
log.Print("sleeping")
62-
time.Sleep(5 * time.Second) // should have picked up another poll, but it is cancelled
63-
assert.Equal(t, "[polls:3]", configManager.GetMetrics())
64-
6544
}

optimizely/utils/metrics.go

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)