Skip to content

Commit 6edaa6d

Browse files
refactor decision service, bucketer, and user profile service. The decision service is the only one that needs to contain the others. So, the refactor is a great simplification
1 parent 93ab621 commit 6edaa6d

File tree

10 files changed

+70
-143
lines changed

10 files changed

+70
-143
lines changed

OptimizelySDK/Extensions/OptimizelyManager+Extension.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ extension OptimizelyManager {
1212
func registerServices(sdkKey:String,
1313
logger:OPTLogger,
1414
eventDispatcher:OPTEventDispatcher,
15-
userProfileService:OPTUserProfileService,
1615
datafileHandler:OPTDatafileHandler,
17-
bucketer:OPTBucketer,
1816
decisionService:OPTDecisionService,
1917
notificationCenter:OPTNotificationCenter) {
2018
// bind it as a non-singleton. so, we will create an instance anytime injected.
@@ -26,10 +24,6 @@ extension OptimizelyManager {
2624
// this is bound a reusable singleton. so, if we re-initalize, we will keep this.
2725
HandlerRegistryService.shared.registerBinding(binder:Binder<OPTNotificationCenter>(service: OPTNotificationCenter.self).singetlon().reInitializeStrategy(strategy: .reUse).using(instance:notificationCenter).sdkKey(key: sdkKey))
2826

29-
// this is a singleton but it has a reIntializeStrategy of reCreate. So, we create a new
30-
// instance on re-initialize.
31-
HandlerRegistryService.shared.registerBinding(binder:Binder<OPTBucketer>(service: OPTBucketer.self).singetlon().using(instance:bucketer).sdkKey(key: sdkKey))
32-
3327
// the decision service is also a singleton that will reCreate on re-initalize
3428
HandlerRegistryService.shared.registerBinding(binder:Binder<OPTDecisionService>(service: OPTDecisionService.self).singetlon().using(instance:decisionService).sdkKey(key: sdkKey))
3529

@@ -39,9 +33,5 @@ extension OptimizelyManager {
3933
// This is a singleton and might be a good candidate for reuse. The handler supports mulitple
4034
// sdk keys without having to be created for every key.
4135
HandlerRegistryService.shared.registerBinding(binder:Binder<OPTDatafileHandler>(service: OPTDatafileHandler.self).singetlon().reInitializeStrategy(strategy: .reUse).to(factory: type(of:datafileHandler).init).using(instance: datafileHandler).sdkKey(key: sdkKey))
42-
43-
// the user profile service is also a singleton using eh passed in version.
44-
HandlerRegistryService.shared.registerBinding(binder:Binder<OPTUserProfileService>(service: OPTUserProfileService.self).singetlon().reInitializeStrategy(strategy:.reUse).using(instance:userProfileService).to(factory: type(of:userProfileService).init).sdkKey(key: sdkKey))
45-
4636
}
4737
}

OptimizelySDK/Implementation/DefaultBucketer.swift

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,14 @@ class DefaultBucketer : OPTBucketer {
2222
let MAX_HASH_SEED:UInt64 = 1
2323
var MAX_HASH_VALUE:UInt64?
2424

25-
private var config:ProjectConfig!
2625
private lazy var logger = HandlerRegistryService.shared.injectLogger()
2726

28-
internal required init(config:ProjectConfig) {
29-
self.config = config
30-
31-
MAX_HASH_VALUE = MAX_HASH_SEED << 32
32-
}
33-
3427
// [Jae]: let be configured after initialized (with custom DecisionHandler set up on OPTManger initialization)
3528
init() {
3629
MAX_HASH_VALUE = MAX_HASH_SEED << 32
3730
}
3831

39-
func initialize(config:ProjectConfig) {
40-
self.config = config
41-
}
42-
43-
func bucketToExperiment(group: Group, bucketingId: String) -> Experiment? {
32+
func bucketToExperiment(config:ProjectConfig, group: Group, bucketingId: String) -> Experiment? {
4433
let hashId = makeHashIdFromBucketingId(bucketingId: bucketingId, entityId: group.id)
4534
let bucketValue = self.generateBucketValue(bucketingId: hashId)
4635

@@ -73,7 +62,7 @@ class DefaultBucketer : OPTBucketer {
7362
return nil
7463
}
7564

76-
func bucketExperiment(experiment: Experiment, bucketingId: String) -> Variation? {
65+
func bucketExperiment(config:ProjectConfig, experiment: Experiment, bucketingId: String) -> Variation? {
7766
var ok = true
7867
// check for mutex
7968
let group = config.project.groups.filter({ if let _ = $0.experiments.filter({$0.id == experiment.id }).first { return true } else { return false }}).first
@@ -83,7 +72,7 @@ class DefaultBucketer : OPTBucketer {
8372
case .overlapping:
8473
break;
8574
case .random:
86-
let mutexExperiment = bucketToExperiment(group: group, bucketingId: bucketingId)
75+
let mutexExperiment = bucketToExperiment(config: config, group: group, bucketingId: bucketingId)
8776
if let mutexExperiment = mutexExperiment, mutexExperiment.id == experiment.id {
8877
ok = true
8978
}

OptimizelySDK/Implementation/DefaultDecisionService.swift

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,16 @@
1717
import Foundation
1818

1919
class DefaultDecisionService : OPTDecisionService {
20-
var config:ProjectConfig!
21-
var bucketer:OPTBucketer!
22-
var userProfileService:OPTUserProfileService!
23-
24-
internal required init(config:ProjectConfig, bucketer:OPTBucketer, userProfileService:OPTUserProfileService) {
25-
self.config = config
26-
self.bucketer = bucketer
27-
self.userProfileService = userProfileService
28-
}
2920

30-
// [Jae]: let be configured after initialized (with custom DecisionHandler set up on OPTManger initialization)
31-
init() {}
21+
var bucketer:OPTBucketer
22+
var userProfileService:OPTUserProfileService
3223

33-
func initialize(config:ProjectConfig, bucketer:OPTBucketer, userProfileService:OPTUserProfileService) {
34-
self.config = config
24+
init(bucketer: OPTBucketer, userProfileService:OPTUserProfileService) {
3525
self.bucketer = bucketer
3626
self.userProfileService = userProfileService
3727
}
38-
39-
func getVariation(userId:String, experiment: Experiment, attributes: OptimizelyAttributes) -> Variation? {
28+
29+
func getVariation(config:ProjectConfig, userId:String, experiment: Experiment, attributes: OptimizelyAttributes) -> Variation? {
4030
let experimentId = experiment.id;
4131

4232
// Acquire bucketingId .
@@ -68,12 +58,13 @@ class DefaultDecisionService : OPTDecisionService {
6858
var bucketedVariation:Variation?
6959
// ---- check if the user passes audience targeting before bucketing ----
7060
if let result = isInExperiment(
61+
config: config,
7162
experiment:experiment,
7263
userId:userId,
7364
attributes:attributes), result == true {
7465

7566
// bucket user into a variation
76-
bucketedVariation = bucketer.bucketExperiment(experiment: experiment, bucketingId:bucketingId)
67+
bucketedVariation = bucketer.bucketExperiment(config: config, experiment: experiment, bucketingId:bucketingId)
7768

7869
if let bucketedVariation = bucketedVariation {
7970
// save to user profile
@@ -85,7 +76,7 @@ class DefaultDecisionService : OPTDecisionService {
8576

8677
}
8778

88-
func isInExperiment(experiment:Experiment, userId:String, attributes: OptimizelyAttributes) -> Bool? {
79+
func isInExperiment(config:ProjectConfig, experiment:Experiment, userId:String, attributes: OptimizelyAttributes) -> Bool? {
8980

9081
if let conditions = experiment.audienceConditions {
9182
switch conditions {
@@ -119,45 +110,46 @@ class DefaultDecisionService : OPTDecisionService {
119110
return true
120111
}
121112

122-
func getExperimentInGroup(group:Group, bucketingId:String) -> Experiment? {
123-
let experiment = bucketer.bucketToExperiment(group:group, bucketingId:bucketingId)
113+
func getExperimentInGroup(config:ProjectConfig, group:Group, bucketingId:String) -> Experiment? {
114+
let experiment = bucketer.bucketToExperiment(config: config, group:group, bucketingId:bucketingId)
124115
if let _ = experiment {
125116
// log
126117
}
127118

128119
return experiment;
129120
}
130121

131-
func getVariationForFeature(featureFlag:FeatureFlag, userId:String, attributes: OptimizelyAttributes) -> (experiment:Experiment?, variation:Variation?)? {
122+
func getVariationForFeature(config:ProjectConfig, featureFlag:FeatureFlag, userId:String, attributes: OptimizelyAttributes) -> (experiment:Experiment?, variation:Variation?)? {
132123
//Evaluate in this order:
133124

134125
//1. Attempt to bucket user into experiment using feature flag.
135126
// Check if the feature flag is under an experiment and the the user is bucketed into one of these experiments
136-
if let variation = getVariationForFeatureExperiment(featureFlag: featureFlag, userId:userId, attributes:attributes) {
127+
if let variation = getVariationForFeatureExperiment(config: config, featureFlag: featureFlag, userId:userId, attributes:attributes) {
137128
return variation
138129
}
139130

140131
//2. Attempt to bucket user into rollout using the feature flag.
141132
// Check if the feature flag has rollout and the user is bucketed into one of it's rules
142-
if let variation = getVariationForFeatureRollout(featureFlag: featureFlag, userId:userId, attributes:attributes) {
133+
if let variation = getVariationForFeatureRollout(config: config, featureFlag: featureFlag, userId:userId, attributes:attributes) {
143134
return (nil, variation)
144135
}
145136

146137
return nil;
147138

148139
}
149140

150-
func getVariationForFeatureGroup(featureFlag: FeatureFlag,
141+
func getVariationForFeatureGroup(config:ProjectConfig,
142+
featureFlag: FeatureFlag,
151143
groupId: String,
152144
userId: String,
153145
attributes: OptimizelyAttributes) -> (experiment:Experiment?, variation:Variation?)? {
154146

155147
let bucketing_id = getBucketingId(userId:userId, attributes:attributes)
156148
if let group = config.project.groups.filter({$0.id == groupId}).first {
157149

158-
if let experiment = getExperimentInGroup(group: group, bucketingId:bucketing_id),
150+
if let experiment = getExperimentInGroup(config: config, group: group, bucketingId:bucketing_id),
159151
featureFlag.experimentIds.contains(experiment.id),
160-
let variation = getVariation(userId:userId, experiment:experiment, attributes:attributes) {
152+
let variation = getVariation(config: config, userId:userId, experiment:experiment, attributes:attributes) {
161153
// log
162154
return (experiment,variation)
163155
}
@@ -169,7 +161,8 @@ class DefaultDecisionService : OPTDecisionService {
169161
return nil
170162
}
171163

172-
func getVariationForFeatureExperiment(featureFlag: FeatureFlag,
164+
func getVariationForFeatureExperiment(config:ProjectConfig,
165+
featureFlag: FeatureFlag,
173166
userId: String,
174167
attributes: OptimizelyAttributes) -> (experiment:Experiment?, variation:Variation?)? {
175168

@@ -178,14 +171,15 @@ class DefaultDecisionService : OPTDecisionService {
178171
// Evaluate each experiment ID and return the first bucketed experiment variation
179172
for experimentId in experimentIds {
180173
if let experiment = config.getExperiment(id: experimentId),
181-
let variation = getVariation(userId: userId, experiment: experiment, attributes: attributes) {
174+
let variation = getVariation(config: config, userId: userId, experiment: experiment, attributes: attributes) {
182175
return (experiment,variation)
183176
}
184177
}
185178
return nil;
186179
}
187180

188-
func getVariationForFeatureRollout(featureFlag: FeatureFlag,
181+
func getVariationForFeatureRollout(config:ProjectConfig,
182+
featureFlag: FeatureFlag,
189183
userId: String,
190184
attributes: OptimizelyAttributes) -> Variation? {
191185

@@ -200,16 +194,16 @@ class DefaultDecisionService : OPTDecisionService {
200194
let rolloutRules = rollout.experiments
201195
// Evaluate all rollout rules except for last one
202196
for experiment in rolloutRules[0..<rolloutRules.count.advanced(by: -1)] {
203-
if isInExperiment(experiment: experiment, userId: userId, attributes: attributes) ?? false {
204-
if let variation = bucketer.bucketExperiment(experiment: experiment, bucketingId: bucketingId) {
197+
if isInExperiment(config: config, experiment: experiment, userId: userId, attributes: attributes) ?? false {
198+
if let variation = bucketer.bucketExperiment(config:config, experiment: experiment, bucketingId: bucketingId) {
205199
return variation
206200
}
207201
}
208202
}
209203
// Evaluate fall back rule / last rule now
210204
let experiment = rolloutRules[rolloutRules.count - 1];
211-
if isInExperiment(experiment: experiment, userId: userId, attributes: attributes) ?? false {
212-
return bucketer.bucketExperiment(experiment: experiment, bucketingId: bucketingId)
205+
if isInExperiment(config: config, experiment: experiment, userId: userId, attributes: attributes) ?? false {
206+
return bucketer.bucketExperiment(config: config, experiment: experiment, bucketingId: bucketingId)
213207
}
214208

215209
return nil

OptimizelySDK/Optimizely/OptimizelyManager.swift

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,10 @@ open class OptimizelyManager: NSObject {
3030
return HandlerRegistryService.shared.injectEventDispatcher(sdkKey: self.sdkKey)!
3131
}
3232
}
33-
var userProfileService: OPTUserProfileService {
34-
get {
35-
return HandlerRegistryService.shared.injectUserProfileService(sdkKey: self.sdkKey)!
36-
}
37-
}
3833
let periodicDownloadInterval: Int
3934

4035
// MARK: - Default Services
4136

42-
var bucketer: OPTBucketer {
43-
get {
44-
return HandlerRegistryService.shared.injectBucketer(sdkKey: self.sdkKey)!
45-
}
46-
}
4737
var decisionService: OPTDecisionService {
4838
get {
4939
return HandlerRegistryService.shared.injectDecisionService(sdkKey: self.sdkKey)!
@@ -82,13 +72,13 @@ open class OptimizelyManager: NSObject {
8272

8373
super.init()
8474

75+
let bucketer = DefaultBucketer()
76+
let userProfileService = userProfileService ?? DefaultUserProfileService()
8577
self.registerServices(sdkKey: sdkKey,
8678
logger: logger ?? DefaultLogger(),
8779
eventDispatcher: eventDispatcher ?? DefaultEventDispatcher.sharedInstance,
88-
userProfileService: userProfileService ?? DefaultUserProfileService(),
8980
datafileHandler: DefaultDatafileHandler(),
90-
bucketer: DefaultBucketer(),
91-
decisionService: DefaultDecisionService(),
81+
decisionService: DefaultDecisionService(bucketer: bucketer, userProfileService: userProfileService),
9282
notificationCenter: DefaultNotificationCenter())
9383

9484
}
@@ -158,11 +148,6 @@ open class OptimizelyManager: NSObject {
158148
// this isn't really necessary because the try would throw if there is a problem. But, we want to avoid using bang so we do another let binding.
159149
guard let config = self.config else { throw OptimizelyError.dataFileInvalid }
160150

161-
// TODO: fix these to throw errors
162-
bucketer.initialize(config: config)
163-
decisionService.initialize(config: config,
164-
bucketer: self.bucketer,
165-
userProfileService: self.userProfileService)
166151
if periodicDownloadInterval > 0 {
167152
datafileHandler.stopPeriodicUpdates(sdkKey: self.sdkKey)
168153
datafileHandler.startPeriodicUpdates(sdkKey: self.sdkKey, updateInterval: periodicDownloadInterval) { data in
@@ -181,12 +166,6 @@ open class OptimizelyManager: NSObject {
181166
HandlerRegistryService.shared.reInitializeComponent(service: component, sdkKey: self.sdkKey)
182167
}
183168

184-
// now reinitialize with the new config.
185-
self.bucketer.initialize(config: config)
186-
self.decisionService.initialize(config: config,
187-
bucketer: self.bucketer,
188-
userProfileService: self.userProfileService)
189-
190169
}
191170

192171
self.notificationCenter.sendNotifications(type:
@@ -347,7 +326,7 @@ open class OptimizelyManager: NSObject {
347326
}
348327

349328
// fix DecisionService to throw error
350-
guard let variation = decisionService.getVariation(userId: userId, experiment: experiment, attributes: attributes ?? OptimizelyAttributes()) else {
329+
guard let variation = decisionService.getVariation(config: config, userId: userId, experiment: experiment, attributes: attributes ?? OptimizelyAttributes()) else {
351330
throw OptimizelyError.variationUnknown
352331
}
353332

@@ -417,7 +396,7 @@ open class OptimizelyManager: NSObject {
417396
}
418397

419398
// fix DecisionService to throw error
420-
let pair = decisionService.getVariationForFeature(featureFlag: featureFlag, userId: userId, attributes: attributes ?? OptimizelyAttributes())
399+
let pair = decisionService.getVariationForFeature(config: config, featureFlag: featureFlag, userId: userId, attributes: attributes ?? OptimizelyAttributes())
421400

422401
guard let variation = pair?.variation else {
423402
throw OptimizelyError.variationUnknown
@@ -561,7 +540,7 @@ open class OptimizelyManager: NSObject {
561540
if attributes != nil {
562541
_attributes = attributes!
563542
}
564-
if let decision = self.decisionService.getVariationForFeature(featureFlag: featureFlag, userId: userId, attributes: _attributes) {
543+
if let decision = self.decisionService.getVariationForFeature(config: config, featureFlag: featureFlag, userId: userId, attributes: _attributes) {
565544
if let featureVariableUsage = decision.variation?.variables?.filter({$0.id == variable.id}).first {
566545
defaultValue = featureVariableUsage.value
567546
}

0 commit comments

Comments
 (0)