Skip to content

Commit 2b39d30

Browse files
committed
WIP evaluator
AI-Session-Id: 095253e8-7e1c-4578-9779-bf96395021cf AI-Tool: claude-code AI-Model: unknown
1 parent 5f608ac commit 2b39d30

File tree

65 files changed

+497
-774
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+497
-774
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ target
99
.project
1010
.settings
1111
.DS_Store
12-
dependency-reduced-pom.xml
12+
dependency-reduced-pom.xml

client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@
167167
</properties>
168168

169169
<dependencies>
170+
<dependency>
171+
<groupId>io.split.client</groupId>
172+
<artifactId>targeting-engine</artifactId>
173+
<version>${project.version}</version>
174+
</dependency>
170175
<dependency>
171176
<groupId>io.split.client</groupId>
172177
<artifactId>pluggable-storage</artifactId>

client/src/main/java/io/split/client/CacheUpdaterService.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package io.split.client;
22

3-
import com.google.common.collect.Lists;
43
import io.split.client.dtos.ConditionType;
5-
import io.split.client.dtos.MatcherCombiner;
64
import io.split.client.dtos.Partition;
75
import io.split.engine.experiments.ParsedCondition;
86
import io.split.engine.experiments.ParsedSplit;
9-
import io.split.engine.matchers.AllKeysMatcher;
10-
import io.split.engine.matchers.AttributeMatcher;
11-
import io.split.engine.matchers.CombiningMatcher;
12-
import io.split.engine.matchers.strings.WhitelistMatcher;
7+
import io.split.rules.matchers.AllKeysMatcher;
8+
import io.split.rules.matchers.AttributeMatcher;
9+
import io.split.rules.matchers.CombiningMatcher;
10+
import io.split.rules.matchers.WhitelistMatcher;
1311
import io.split.grammar.Treatments;
1412
import io.split.storages.SplitCacheProducer;
1513

@@ -22,15 +20,15 @@
2220
import java.util.HashMap;
2321
import java.util.stream.Collectors;
2422

25-
import static com.google.common.base.Preconditions.checkNotNull;
23+
import java.util.Objects;
2624

2725
public final class CacheUpdaterService {
2826

2927
private static String LOCALHOST = "localhost";
3028
private SplitCacheProducer _splitCacheProducer;
3129

3230
public CacheUpdaterService(SplitCacheProducer splitCacheProducer) {
33-
_splitCacheProducer = checkNotNull(splitCacheProducer);
31+
_splitCacheProducer = Objects.requireNonNull(splitCacheProducer);
3432
}
3533

3634
public void updateCache(Map<SplitAndKey, LocalhostSplit> map) {
@@ -78,9 +76,10 @@ private List<ParsedCondition> getConditions(String splitKey, ParsedSplit split,
7876

7977
private ParsedCondition createWhitelistCondition(String splitKey, Partition partition) {
8078
ParsedCondition parsedCondition = new ParsedCondition(ConditionType.WHITELIST,
81-
new CombiningMatcher(MatcherCombiner.AND,
82-
Lists.newArrayList(new AttributeMatcher(null, new WhitelistMatcher(Lists.newArrayList(splitKey)), false))),
83-
Lists.newArrayList(partition), splitKey);
79+
new CombiningMatcher(CombiningMatcher.Combiner.AND,
80+
new java.util.ArrayList<>(java.util.Arrays.asList(
81+
new AttributeMatcher(null, new WhitelistMatcher(java.util.Arrays.asList(splitKey)), false)))),
82+
new java.util.ArrayList<>(java.util.Arrays.asList(partition)), splitKey);
8483
return parsedCondition;
8584
}
8685

@@ -89,9 +88,9 @@ private ParsedCondition createRolloutCondition(Partition partition) {
8988
rolloutPartition.treatment = "-";
9089
rolloutPartition.size = 0;
9190
ParsedCondition parsedCondition = new ParsedCondition(ConditionType.ROLLOUT,
92-
new CombiningMatcher(MatcherCombiner.AND,
93-
Lists.newArrayList(new AttributeMatcher(null, new AllKeysMatcher(), false))),
94-
Lists.newArrayList(partition, rolloutPartition), "LOCAL");
91+
new CombiningMatcher(CombiningMatcher.Combiner.AND,
92+
new java.util.ArrayList<>(java.util.Arrays.asList(new AttributeMatcher(null, new AllKeysMatcher(), false)))),
93+
new java.util.ArrayList<>(java.util.Arrays.asList(partition, rolloutPartition)), "LOCAL");
9594

9695
return parsedCondition;
9796
}

client/src/main/java/io/split/client/api/SplitView.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.List;
1212
import java.util.Map;
1313
import java.util.Set;
14+
import java.util.stream.Collectors;
1415

1516

1617
/**
@@ -51,7 +52,13 @@ public static SplitView fromParsedSplit(ParsedSplit parsedSplit) {
5152
splitView.configs = parsedSplit.configurations() == null? Collections.<String, String>emptyMap() : parsedSplit.configurations() ;
5253
splitView.impressionsDisabled = parsedSplit.impressionsDisabled();
5354
splitView.prerequisites = parsedSplit.prerequisitesMatcher() != null ?
54-
parsedSplit.prerequisitesMatcher().getPrerequisites(): new ArrayList<>();
55+
parsedSplit.prerequisitesMatcher().getPrerequisites().stream()
56+
.map(p -> {
57+
Prerequisites prereq = new Prerequisites();
58+
prereq.featureFlagName = p.featureFlagName();
59+
prereq.treatments = p.treatments();
60+
return prereq;
61+
}).collect(Collectors.toList()) : new ArrayList<>();
5562

5663
return splitView;
5764
}

client/src/main/java/io/split/client/impressions/ImpressionHasher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.split.client.impressions;
22

3-
import io.split.client.utils.MurmurHash3;
3+
import io.split.rules.bucketing.MurmurHash3;
44

55
public class ImpressionHasher {
66

client/src/main/java/io/split/engine/evaluator/EvaluationContext.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package io.split.engine.evaluator;
22

3+
import io.split.client.dtos.ExcludedSegments;
4+
import io.split.engine.experiments.ParsedCondition;
5+
import io.split.engine.experiments.ParsedRuleBasedSegment;
6+
import io.split.rules.engine.EvaluationResult;
37
import io.split.storages.RuleBasedSegmentCacheConsumer;
48
import io.split.storages.SegmentCacheConsumer;
59

10+
import java.util.List;
11+
import java.util.Map;
612
import java.util.Objects;
713

8-
public class EvaluationContext {
14+
public class EvaluationContext implements io.split.rules.engine.EvaluationContext {
915
private final Evaluator _evaluator;
1016
private final SegmentCacheConsumer _segmentCacheConsumer;
1117
private final RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer;
@@ -28,4 +34,40 @@ public SegmentCacheConsumer getSegmentCache() {
2834
public RuleBasedSegmentCacheConsumer getRuleBasedSegmentCache() {
2935
return _ruleBasedSegmentCacheConsumer;
3036
}
37+
38+
@Override
39+
public EvaluationResult evaluate(String matchingKey, String bucketingKey, String ruleName, Map<String, Object> attributes) {
40+
EvaluatorImp.TreatmentLabelAndChangeNumber r = _evaluator.evaluateFeature(matchingKey, bucketingKey, ruleName, attributes);
41+
return new EvaluationResult(r.treatment, r.label, r.changeNumber, r.configurations, r.track);
42+
}
43+
44+
@Override
45+
public boolean isInSegment(String segmentName, String key) {
46+
return _segmentCacheConsumer.isInSegment(segmentName, key);
47+
}
48+
49+
@Override
50+
public boolean isInRuleBasedSegment(String segmentName, String key, String bucketingKey, Map<String, Object> attributes) {
51+
ParsedRuleBasedSegment parsedRuleBasedSegment = _ruleBasedSegmentCacheConsumer.get(segmentName);
52+
if (parsedRuleBasedSegment == null) {
53+
return false;
54+
}
55+
if (parsedRuleBasedSegment.excludedKeys().contains(key)) {
56+
return false;
57+
}
58+
for (ExcludedSegments excludedSegment : parsedRuleBasedSegment.excludedSegments()) {
59+
if (excludedSegment.isStandard() && _segmentCacheConsumer.isInSegment(excludedSegment.name, key)) {
60+
return false;
61+
}
62+
if (excludedSegment.isRuleBased() && isInRuleBasedSegment(excludedSegment.name, key, bucketingKey, attributes)) {
63+
return false;
64+
}
65+
}
66+
for (ParsedCondition condition : parsedRuleBasedSegment.parsedConditions()) {
67+
if (condition.matcher().match(key, bucketingKey, attributes, this)) {
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
3173
}

client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java

Lines changed: 15 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package io.split.engine.evaluator;
22

3-
import io.split.client.dtos.ConditionType;
43
import io.split.client.dtos.FallbackTreatment;
54
import io.split.client.dtos.FallbackTreatmentCalculator;
65
import io.split.client.exceptions.ChangeNumberExceptionWrapper;
7-
import io.split.engine.experiments.ParsedCondition;
86
import io.split.engine.experiments.ParsedSplit;
9-
import io.split.engine.splitter.Splitter;
7+
import io.split.rules.engine.EvaluationResult;
8+
import io.split.rules.engine.TargetingEngine;
9+
import io.split.rules.engine.TargetingEngineImpl;
10+
import io.split.rules.exceptions.VersionedExceptionWrapper;
1011
import io.split.storages.RuleBasedSegmentCacheConsumer;
1112
import io.split.storages.SegmentCacheConsumer;
1213
import io.split.storages.SplitCacheConsumer;
@@ -28,6 +29,7 @@ public class EvaluatorImp implements Evaluator {
2829
private final EvaluationContext _evaluationContext;
2930
private final SplitCacheConsumer _splitCacheConsumer;
3031
private final FallbackTreatmentCalculator _fallbackTreatmentCalculator;
32+
private final TargetingEngine _targetingEngine;
3133
private final String _evaluatorException = "Evaluator Exception";
3234

3335
public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache,
@@ -37,6 +39,7 @@ public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer
3739
_segmentCacheConsumer = Objects.requireNonNull(segmentCache);
3840
_evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer);
3941
_fallbackTreatmentCalculator = fallbackTreatmentCalculator;
42+
_targetingEngine = new TargetingEngineImpl();
4043
}
4144

4245
@Override
@@ -102,100 +105,23 @@ private List<String> getFeatureFlagNamesByFlagSets(List<String> flagSets) {
102105

103106
/**
104107
* @param matchingKey MUST NOT be null
105-
* @param bucketingKey
108+
* @param bucketingKey may be null
106109
* @param parsedSplit MUST NOT be null
107-
* @param attributes MUST NOT be null
110+
* @param attributes may be null
108111
* @return
109112
* @throws ChangeNumberExceptionWrapper
110113
*/
111-
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map<String,
112-
Object> attributes) throws ChangeNumberExceptionWrapper {
114+
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit,
115+
Map<String, Object> attributes) throws ChangeNumberExceptionWrapper {
113116
try {
114-
String config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
115-
if (parsedSplit.killed()) {
116-
return new TreatmentLabelAndChangeNumber(
117-
parsedSplit.defaultTreatment(),
118-
Labels.KILLED,
119-
parsedSplit.changeNumber(),
120-
config,
121-
parsedSplit.impressionsDisabled());
122-
}
123-
124-
String bk = getBucketingKey(bucketingKey, matchingKey);
125-
126-
if (!parsedSplit.prerequisitesMatcher().match(matchingKey, bk, attributes, _evaluationContext)) {
127-
return new TreatmentLabelAndChangeNumber(
128-
parsedSplit.defaultTreatment(),
129-
Labels.PREREQUISITES_NOT_MET,
130-
parsedSplit.changeNumber(),
131-
config,
132-
parsedSplit.impressionsDisabled());
133-
}
134-
135-
/*
136-
* There are three parts to a single Feature flag: 1) Whitelists 2) Traffic Allocation
137-
* 3) Rollout. The flag inRollout is there to understand when we move into the Rollout
138-
* section. This is because we need to make sure that the Traffic Allocation
139-
* computation happens after the whitelist but before the rollout.
140-
*/
141-
boolean inRollout = false;
142-
143-
for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) {
144-
145-
if (checkRollout(inRollout, parsedCondition)) {
146-
147-
if (parsedSplit.trafficAllocation() < 100) {
148-
// if the traffic allocation is 100%, no need to do anything special.
149-
int bucket = Splitter.getBucket(bk, parsedSplit.trafficAllocationSeed(), parsedSplit.algo());
150-
151-
if (bucket > parsedSplit.trafficAllocation()) {
152-
// out of split
153-
config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
154-
return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.NOT_IN_SPLIT,
155-
parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled());
156-
}
157-
158-
}
159-
inRollout = true;
160-
}
161-
162-
if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, _evaluationContext)) {
163-
String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo());
164-
config = getConfig(parsedSplit, treatment);
165-
return new TreatmentLabelAndChangeNumber(
166-
treatment,
167-
parsedCondition.label(),
168-
parsedSplit.changeNumber(),
169-
config,
170-
parsedSplit.impressionsDisabled());
171-
}
172-
}
173-
174-
config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
175-
176-
return new TreatmentLabelAndChangeNumber(
177-
parsedSplit.defaultTreatment(),
178-
Labels.DEFAULT_RULE,
179-
parsedSplit.changeNumber(),
180-
config,
181-
parsedSplit.impressionsDisabled());
182-
} catch (Exception e) {
183-
throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber());
117+
EvaluationResult r = _targetingEngine.evaluate(matchingKey, bucketingKey,
118+
parsedSplit.targetingRule(), attributes, _evaluationContext);
119+
return new TreatmentLabelAndChangeNumber(r.treatment, r.label, r.version, r.config, r.impressionsDisabled);
120+
} catch (VersionedExceptionWrapper e) {
121+
throw new ChangeNumberExceptionWrapper(e.wrappedException(), e.version());
184122
}
185123
}
186124

187-
private boolean checkRollout(boolean inRollout, ParsedCondition parsedCondition) {
188-
return (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT);
189-
}
190-
191-
private String getBucketingKey(String bucketingKey, String matchingKey) {
192-
return (bucketingKey == null) ? matchingKey : bucketingKey;
193-
}
194-
195-
private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) {
196-
return parsedSplit.configurations() != null ? parsedSplit.configurations().get(returnedTreatment) : null;
197-
}
198-
199125
private String getFallbackConfig(FallbackTreatment fallbackTreatment) {
200126
if (fallbackTreatment.getConfig() != null) {
201127
return fallbackTreatment.getConfig();

client/src/main/java/io/split/engine/experiments/ParsedCondition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import io.split.client.dtos.ConditionType;
44
import io.split.client.dtos.Partition;
5-
import io.split.engine.matchers.CombiningMatcher;
5+
import io.split.rules.matchers.CombiningMatcher;
66

77
import java.util.List;
88

client/src/main/java/io/split/engine/experiments/ParsedRuleBasedSegment.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package io.split.engine.experiments;
22

3-
import com.google.common.collect.ImmutableList;
43
import io.split.client.dtos.ExcludedSegments;
5-
import io.split.engine.matchers.AttributeMatcher;
6-
import io.split.engine.matchers.UserDefinedSegmentMatcher;
4+
import io.split.rules.matchers.AttributeMatcher;
5+
import io.split.rules.matchers.UserDefinedSegmentMatcher;
76

87
import java.util.List;
98
import java.util.Set;
@@ -12,7 +11,7 @@
1211
public class ParsedRuleBasedSegment {
1312

1413
private final String _ruleBasedSegment;
15-
private final ImmutableList<ParsedCondition> _parsedCondition;
14+
private final List<ParsedCondition> _parsedCondition;
1615
private final String _trafficTypeName;
1716
private final long _changeNumber;
1817
private final List<String> _excludedKeys;
@@ -45,7 +44,7 @@ public ParsedRuleBasedSegment(
4544
List<ExcludedSegments> excludedSegments
4645
) {
4746
_ruleBasedSegment = ruleBasedSegment;
48-
_parsedCondition = ImmutableList.copyOf(matcherAndSplits);
47+
_parsedCondition = java.util.Collections.unmodifiableList(new java.util.ArrayList<>(matcherAndSplits));
4948
_trafficTypeName = trafficTypeName;
5049
_changeNumber = changeNumber;
5150
_excludedKeys = excludedKeys;

0 commit comments

Comments
 (0)