@@ -29,12 +29,6 @@ struct VariationDecision {
29
29
var cmabUUID : String ?
30
30
}
31
31
32
- enum OperationType {
33
- case async
34
- case sync
35
- }
36
-
37
- typealias OPType = OperationType
38
32
typealias UserProfile = OPTUserProfileService . UPProfile
39
33
40
34
class DefaultDecisionService : OPTDecisionService {
@@ -44,14 +38,13 @@ class DefaultDecisionService: OPTDecisionService {
44
38
let group : DispatchGroup = DispatchGroup ( )
45
39
// thread-safe lazy logger load (after HandlerRegisterService ready)
46
40
private let threadSafeLogger = ThreadSafeLogger ( )
47
-
48
41
// user-profile-service read-modify-write lock for supporting multiple clients
49
42
static let upsRMWLock = DispatchQueue ( label: " ups-rmw " )
50
43
51
44
var logger : OPTLogger {
52
45
return threadSafeLogger. logger
53
46
}
54
-
47
+
55
48
init ( userProfileService: OPTUserProfileService ,
56
49
cmabService: CmabService = DefaultCmabService . createDefault ( ) ) {
57
50
self . bucketer = DefaultBucketer ( )
@@ -74,24 +67,26 @@ class DefaultDecisionService: OPTDecisionService {
74
67
/// - config: The project configuration containing experiment and feature details.
75
68
/// - experiment: The CMAB experiment to evaluate.
76
69
/// - user: The user context containing user ID and attributes.
70
+ /// - bucketingId: User bucketing id
71
+ /// - isAsync: Controls synchronous or asynchronous decision.
77
72
/// - options: Optional decision options (e.g., ignore user profile service).
78
73
/// - Returns: A `CMABDecisionResult` containing the CMAB decisions( variation id, cmabUUID) with reasons
79
74
80
75
private func getDecisionForCmabExperiment( config: ProjectConfig ,
81
76
experiment: Experiment ,
82
77
user: OptimizelyUserContext ,
83
78
bucketingId: String ,
84
- opType : OPType ,
79
+ isAsync : Bool ,
85
80
options: [ OptimizelyDecideOption ] ? ) -> DecisionResponse < VariationDecision > {
86
81
let reasons = DecisionReasons ( options: options)
87
82
guard let cmab = experiment. cmab else {
88
83
logger. e ( " The experiment isn't a CMAB experiment " )
89
84
return DecisionResponse ( result: nil , reasons: reasons)
90
85
}
91
86
92
- guard opType == . async else {
87
+ guard isAsync else {
93
88
let info = LogMessage . cmabNotSupportedInSyncMode
94
- logger. w ( info)
89
+ logger. e ( info)
95
90
reasons. addInfo ( info)
96
91
return DecisionResponse ( result: nil , reasons: reasons)
97
92
}
@@ -103,18 +98,18 @@ class DefaultDecisionService: OPTDecisionService {
103
98
if let _reasons = bucketedResponse? . reasons {
104
99
reasons. merge ( _reasons)
105
100
}
106
-
101
+
107
102
let entityId = bucketedResponse? . result
108
103
109
- // this means the user is not in the cmab experiment
104
+ // This means the user is not in the cmab experiment
110
105
if entityId == nil {
111
106
let info = LogMessage . userNotInCmabExperiment ( user. userId, experiment. key)
112
107
logger. d ( info)
113
108
reasons. addInfo ( info)
114
109
return DecisionResponse ( result: nil , reasons: reasons)
115
110
}
116
111
117
- /// Fetch CMAB decision
112
+ // Fetch CMAB decision
118
113
let response = cmabService. getDecision ( config: config, userContext: user, ruleId: experiment. id, options: options ?? [ ] )
119
114
var cmabDecision : CmabDecision ?
120
115
switch response {
@@ -159,31 +154,14 @@ class DefaultDecisionService: OPTDecisionService {
159
154
profileTracker? . loadUserProfile ( )
160
155
}
161
156
162
- let response = getVariation ( config: config, experiment: experiment, user: user, userProfileTracker: profileTracker)
157
+ // isAsync to `false` for backward compatibility
158
+ let response = getVariation ( config: config, experiment: experiment, user: user, isAsync: false , userProfileTracker: profileTracker)
163
159
164
160
if ( !ignoreUPS) {
165
161
profileTracker? . save ( )
166
162
}
167
163
168
- return response
169
- }
170
-
171
- /// Determines the variation for a user in an experiment, considering user profile and decision rules.
172
- /// - Parameters:
173
- /// - config: The project configuration.
174
- /// - experiment: The experiment to evaluate.
175
- /// - user: The user context.
176
- /// - options: Optional decision options.
177
- /// - userProfileTracker: Optional tracker for user profile data.
178
- /// - Returns: A `DecisionResponse` with the variation (if any) and decision reasons.
179
- func getVariation( config: ProjectConfig ,
180
- experiment: Experiment ,
181
- user: OptimizelyUserContext ,
182
- options: [ OptimizelyDecideOption ] ? = nil ,
183
- userProfileTracker: UserProfileTracker ? ) -> DecisionResponse < Variation > {
184
- let decisionResponse = self . getVariation ( config: config, experiment: experiment, user: user, opType: . sync, userProfileTracker: userProfileTracker)
185
-
186
- return DecisionResponse ( result: decisionResponse. result? . variation, reasons: decisionResponse. reasons)
164
+ return DecisionResponse ( result: response. result? . variation, reasons: response. reasons)
187
165
}
188
166
189
167
/// Determines the variation for a user in an experiment, considering user profile and decision rules.
@@ -192,14 +170,14 @@ class DefaultDecisionService: OPTDecisionService {
192
170
/// - experiment: The experiment to evaluate.
193
171
/// - user: The user context.
194
172
/// - options: Optional decision options.
195
- /// - opType: Operation type, either sync or async
173
+ /// - isAsync: Controls synchronous or asynchronous decision.
196
174
/// - userProfileTracker: Optional tracker for user profile data.
197
175
/// - Returns: A `DecisionResponse` with the variation (if any) and decision reasons.
198
176
func getVariation( config: ProjectConfig ,
199
177
experiment: Experiment ,
200
178
user: OptimizelyUserContext ,
201
179
options: [ OptimizelyDecideOption ] ? = nil ,
202
- opType : OPType ,
180
+ isAsync : Bool ,
203
181
userProfileTracker: UserProfileTracker ? ) -> DecisionResponse < VariationDecision > {
204
182
let reasons = DecisionReasons ( options: options)
205
183
let userId = user. userId
@@ -241,7 +219,7 @@ class DefaultDecisionService: OPTDecisionService {
241
219
reasons. addInfo ( info)
242
220
}
243
221
244
- /// Load variation from tracker
222
+ // Load variation from tracker
245
223
if let profile = userProfileTracker? . userProfile,
246
224
let variationId = getVariationIdFromProfile ( profile: profile, experimentId: experimentId) ,
247
225
let variation = experiment. getVariation ( id: variationId) {
@@ -270,12 +248,12 @@ class DefaultDecisionService: OPTDecisionService {
270
248
experiment: experiment,
271
249
user: user,
272
250
bucketingId: bucketingId,
273
- opType : opType ,
251
+ isAsync : isAsync ,
274
252
options: options)
275
253
reasons. merge ( cmabDecisionResponse. reasons)
276
254
variationDecision = cmabDecisionResponse. result
277
255
} else {
278
- /// bucket user into a variation
256
+ // bucket user into a variation
279
257
let decisionResponse = bucketer. bucketExperiment ( config: config,
280
258
experiment: experiment,
281
259
bucketingId: bucketingId)
@@ -318,17 +296,25 @@ class DefaultDecisionService: OPTDecisionService {
318
296
featureFlag: FeatureFlag ,
319
297
user: OptimizelyUserContext ,
320
298
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < FeatureDecision > {
321
-
322
- self . getVariationForFeature ( config: config, featureFlag: featureFlag, user: user, opType : . sync , options: options)
299
+ // isAsync to `false` for backward compatibility
300
+ self . getVariationForFeature ( config: config, featureFlag: featureFlag, user: user, isAsync : false , options: options)
323
301
}
324
302
303
+ /// Determines the feature decision for a user for a specific feature flag.
304
+ /// - Parameters:
305
+ /// - config: The project configuration.
306
+ /// - featureFlag: The feature flag to evaluate.
307
+ /// - user: The user context.
308
+ /// - isAsync: Controls synchronous or asynchronous decision.
309
+ /// - options: Optional decision options.
310
+ /// - Returns: A `DecisionResponse` with the feature decision (if any) and reasons.
325
311
func getVariationForFeature( config: ProjectConfig ,
326
312
featureFlag: FeatureFlag ,
327
313
user: OptimizelyUserContext ,
328
- opType : OPType ,
314
+ isAsync : Bool ,
329
315
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < FeatureDecision > {
330
316
331
- let response = getVariationForFeatureList ( config: config, featureFlags: [ featureFlag] , user: user, opType : opType , options: options) . first
317
+ let response = getVariationForFeatureList ( config: config, featureFlags: [ featureFlag] , user: user, isAsync : isAsync , options: options) . first
332
318
333
319
guard response? . result != nil else {
334
320
let reasons = response? . reasons ?? DecisionReasons ( options: options)
@@ -343,12 +329,13 @@ class DefaultDecisionService: OPTDecisionService {
343
329
/// - config: The project configuration.
344
330
/// - featureFlags: The list of feature flags to evaluate.
345
331
/// - user: The user context.
332
+ /// - isAsync: Controls synchronous or asynchronous decision
346
333
/// - options: Optional decision options.
347
334
/// - Returns: An array of `DecisionResponse` objects, each containing a feature decision and reasons.
348
335
func getVariationForFeatureList( config: ProjectConfig ,
349
336
featureFlags: [ FeatureFlag ] ,
350
337
user: OptimizelyUserContext ,
351
- opType : OPType = . sync ,
338
+ isAsync : Bool ,
352
339
options: [ OptimizelyDecideOption ] ? = nil ) -> [ DecisionResponse < FeatureDecision > ] {
353
340
354
341
let userId = user. userId
@@ -362,7 +349,7 @@ class DefaultDecisionService: OPTDecisionService {
362
349
var decisions = [ DecisionResponse < FeatureDecision > ] ( )
363
350
364
351
for featureFlag in featureFlags {
365
- let flagDecisionResponse = getDecisionForFlag ( config: config, featureFlag: featureFlag, user: user, userProfileTracker: profileTracker, opType : opType , options: options)
352
+ let flagDecisionResponse = getDecisionForFlag ( config: config, featureFlag: featureFlag, user: user, userProfileTracker: profileTracker, isAsync : isAsync , options: options)
366
353
decisions. append ( flagDecisionResponse)
367
354
}
368
355
@@ -380,13 +367,14 @@ class DefaultDecisionService: OPTDecisionService {
380
367
/// - featureFlag: The feature flag to evaluate.
381
368
/// - user: The user context.
382
369
/// - userProfileTracker: Optional tracker for user profile data.
370
+ /// - isAsync: Controls synchronous or asynchronous decision
383
371
/// - options: Optional decision options.
384
372
/// - Returns: A `DecisionResponse` with the feature decision (if any) and reasons.
385
373
func getDecisionForFlag( config: ProjectConfig ,
386
374
featureFlag: FeatureFlag ,
387
375
user: OptimizelyUserContext ,
388
376
userProfileTracker: UserProfileTracker ? = nil ,
389
- opType : OPType ,
377
+ isAsync : Bool ,
390
378
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < FeatureDecision > {
391
379
let reasons = DecisionReasons ( options: options)
392
380
@@ -404,7 +392,7 @@ class DefaultDecisionService: OPTDecisionService {
404
392
}
405
393
}
406
394
407
- let flagExpDecision = getVariationForFeatureExperiments ( config: config, featureFlag: featureFlag, user: user, userProfileTracker: userProfileTracker, opType : opType , options: options)
395
+ let flagExpDecision = getVariationForFeatureExperiments ( config: config, featureFlag: featureFlag, user: user, userProfileTracker: userProfileTracker, isAsync : isAsync , options: options)
408
396
reasons. merge ( flagExpDecision. reasons)
409
397
410
398
if let decision = flagExpDecision. result {
@@ -427,13 +415,14 @@ class DefaultDecisionService: OPTDecisionService {
427
415
/// - featureFlag: The feature flag to evaluate.
428
416
/// - user: The user context.
429
417
/// - userProfileTracker: Optional tracker for user profile data.
418
+ /// - isAsync: Controls synchronous or asynchronous decision
430
419
/// - options: Optional decision options.
431
420
/// - Returns: A `DecisionResponse` with the feature decision (if any) and reasons.
432
421
func getVariationForFeatureExperiments( config: ProjectConfig ,
433
422
featureFlag: FeatureFlag ,
434
423
user: OptimizelyUserContext ,
435
424
userProfileTracker: UserProfileTracker ? = nil ,
436
- opType : OPType = . sync ,
425
+ isAsync : Bool ,
437
426
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < FeatureDecision > {
438
427
let reasons = DecisionReasons ( options: options)
439
428
@@ -453,7 +442,7 @@ class DefaultDecisionService: OPTDecisionService {
453
442
rule: experiment,
454
443
user: user,
455
444
userProfileTracker: userProfileTracker,
456
- opType : opType ,
445
+ isAsync : isAsync ,
457
446
options: options)
458
447
reasons. merge ( decisionResponse. reasons)
459
448
if let result = decisionResponse. result {
@@ -563,7 +552,7 @@ class DefaultDecisionService: OPTDecisionService {
563
552
564
553
let userId = user. userId
565
554
let attributes = user. attributes
566
-
555
+
567
556
// Acquire bucketingId .
568
557
let bucketingId = getBucketingId ( userId: userId, attributes: attributes)
569
558
var bucketedVariation : Variation ?
@@ -614,7 +603,7 @@ class DefaultDecisionService: OPTDecisionService {
614
603
rule: Experiment ,
615
604
user: OptimizelyUserContext ,
616
605
userProfileTracker: UserProfileTracker ? ,
617
- opType : OPType = . sync ,
606
+ isAsync : Bool ,
618
607
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < VariationDecision > {
619
608
let reasons = DecisionReasons ( options: options)
620
609
// check forced-decision first
@@ -632,7 +621,7 @@ class DefaultDecisionService: OPTDecisionService {
632
621
experiment: rule,
633
622
user: user,
634
623
options: options,
635
- opType : opType ,
624
+ isAsync : isAsync ,
636
625
userProfileTracker: userProfileTracker)
637
626
let variationResult = decisionResponse. result
638
627
reasons. merge ( decisionResponse. reasons)
@@ -656,7 +645,7 @@ class DefaultDecisionService: OPTDecisionService {
656
645
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < ( Variation ? , Bool ) > {
657
646
let reasons = DecisionReasons ( options: options)
658
647
var skipToEveryoneElse = false
659
-
648
+
660
649
// check forced-decision first
661
650
662
651
let rule = rules [ ruleIndex]
0 commit comments