@@ -30,25 +30,61 @@ import (
30
30
type RolloutService struct {
31
31
audienceTreeEvaluator evaluator.TreeEvaluator
32
32
experimentBucketerService ExperimentService
33
- logger logging.OptimizelyLogProducer
33
+ logger logging.OptimizelyLogProducer
34
34
}
35
35
36
36
// NewRolloutService returns a new instance of the Rollout service
37
37
func NewRolloutService (sdkKey string ) * RolloutService {
38
38
return & RolloutService {
39
- logger :logging .GetLogger (sdkKey , "RolloutService" ),
39
+ logger : logging .GetLogger (sdkKey , "RolloutService" ),
40
40
audienceTreeEvaluator : evaluator .NewMixedTreeEvaluator (),
41
41
experimentBucketerService : NewExperimentBucketerService (logging .GetLogger (sdkKey , "ExperimentBucketerService" )),
42
42
}
43
43
}
44
44
45
45
// GetDecision returns a decision for the given feature and user context
46
46
func (r RolloutService ) GetDecision (decisionContext FeatureDecisionContext , userContext entities.UserContext ) (FeatureDecision , error ) {
47
+
47
48
featureDecision := FeatureDecision {
48
49
Source : Rollout ,
49
50
}
50
51
feature := decisionContext .Feature
51
52
rollout := feature .Rollout
53
+
54
+ evaluateConditionTree := func (experiment * entities.Experiment ) bool {
55
+ condTreeParams := entities .NewTreeParameters (& userContext , decisionContext .ProjectConfig .GetAudienceMap ())
56
+ evalResult , _ := r .audienceTreeEvaluator .Evaluate (experiment .AudienceConditionTree , condTreeParams )
57
+ if ! evalResult {
58
+ featureDecision .Reason = reasons .FailedRolloutTargeting
59
+ r .logger .Debug (fmt .Sprintf (`User "%s" failed targeting for feature rollout with key "%s".` , userContext .ID , feature .Key ))
60
+ }
61
+ return evalResult
62
+ }
63
+
64
+ getFeatureDecision := func (experiment * entities.Experiment , decision * ExperimentDecision ) (FeatureDecision , error ) {
65
+ // translate the experiment reason into a more rollouts-appropriate reason
66
+ switch decision .Reason {
67
+ case reasons .NotBucketedIntoVariation :
68
+ featureDecision .Decision = Decision {Reason : reasons .FailedRolloutBucketing }
69
+ case reasons .BucketedIntoVariation :
70
+ featureDecision .Decision = Decision {Reason : reasons .BucketedIntoRollout }
71
+ default :
72
+ featureDecision .Decision = decision .Decision
73
+ }
74
+
75
+ featureDecision .Experiment = * experiment
76
+ featureDecision .Variation = decision .Variation
77
+ r .logger .Debug (fmt .Sprintf (`Decision made for user "%s" for feature rollout with key "%s": %s.` , userContext .ID , feature .Key , featureDecision .Reason ))
78
+ return featureDecision , nil
79
+ }
80
+
81
+ getExperimentDecisionContext := func (experiment * entities.Experiment ) ExperimentDecisionContext {
82
+ return ExperimentDecisionContext {
83
+ Experiment : experiment ,
84
+ ProjectConfig : decisionContext .ProjectConfig ,
85
+ }
86
+ }
87
+
52
88
if rollout .ID == "" {
53
89
featureDecision .Reason = reasons .NoRolloutForFeature
54
90
return featureDecision , nil
@@ -60,38 +96,30 @@ func (r RolloutService) GetDecision(decisionContext FeatureDecisionContext, user
60
96
return featureDecision , nil
61
97
}
62
98
63
- // For now, Rollouts is just a single experiment layer
64
- experiment := rollout .Experiments [0 ]
65
- experimentDecisionContext := ExperimentDecisionContext {
66
- Experiment : & experiment ,
67
- ProjectConfig : decisionContext .ProjectConfig ,
68
- }
69
-
70
- // if user fails rollout targeting rule we return out of it
71
- if experiment .AudienceConditionTree != nil {
72
- condTreeParams := entities .NewTreeParameters (& userContext , decisionContext .ProjectConfig .GetAudienceMap ())
73
- evalResult , _ := r .audienceTreeEvaluator .Evaluate (experiment .AudienceConditionTree , condTreeParams )
74
- if ! evalResult {
75
- featureDecision .Reason = reasons .FailedRolloutTargeting
76
- r .logger .Debug (fmt .Sprintf (`User "%s" failed targeting for feature rollout with key "%s".` , userContext .ID , feature .Key ))
77
- return featureDecision , nil
99
+ for index := 0 ; index < numberOfExperiments - 1 ; index ++ {
100
+ experiment := & rollout .Experiments [index ]
101
+ experimentDecisionContext := getExperimentDecisionContext (experiment )
102
+ // Move to next evaluation if condition tree is available and evaluation fails
103
+ if experiment .AudienceConditionTree != nil && ! evaluateConditionTree (experiment ) {
104
+ // Evaluate this user for the next rule
105
+ continue
106
+ }
107
+ decision , _ := r .experimentBucketerService .GetDecision (experimentDecisionContext , userContext )
108
+ if decision .Variation == nil {
109
+ // Evaluate fall back rule / last rule now
110
+ break
78
111
}
112
+ return getFeatureDecision (experiment , & decision )
79
113
}
80
114
81
- decision , _ := r .experimentBucketerService .GetDecision (experimentDecisionContext , userContext )
82
- // translate the experiment reason into a more rollouts-appropriate reason
83
- switch decision .Reason {
84
- case reasons .NotBucketedIntoVariation :
85
- featureDecision .Decision = Decision {Reason : reasons .FailedRolloutBucketing }
86
- case reasons .BucketedIntoVariation :
87
- featureDecision .Decision = Decision {Reason : reasons .BucketedIntoRollout }
88
- default :
89
- featureDecision .Decision = decision .Decision
115
+ // fall back rule / last rule
116
+ experiment := & rollout .Experiments [numberOfExperiments - 1 ]
117
+ experimentDecisionContext := getExperimentDecisionContext (experiment )
118
+ // Move to bucketing if conditionTree is unavailable or evaluation passes
119
+ if experiment .AudienceConditionTree == nil || evaluateConditionTree (experiment ) {
120
+ decision , _ := r .experimentBucketerService .GetDecision (experimentDecisionContext , userContext )
121
+ return getFeatureDecision (experiment , & decision )
90
122
}
91
123
92
- featureDecision .Experiment = experiment
93
- featureDecision .Variation = decision .Variation
94
- r .logger .Debug (fmt .Sprintf (`Decision made for user "%s" for feature rollout with key "%s": %s.` , userContext .ID , feature .Key , featureDecision .Reason ))
95
-
96
124
return featureDecision , nil
97
125
}
0 commit comments