|
| 1 | +# targeting-engine |
| 2 | + |
| 3 | +A generic, zero-dependency rule evaluation engine extracted from the Split Java SDK. Given a parsed `TargetingRule` and a user key, it deterministically returns a treatment. |
| 4 | + |
| 5 | +## Maven Dependency |
| 6 | + |
| 7 | +```xml |
| 8 | +<dependency> |
| 9 | + <groupId>io.split.client</groupId> |
| 10 | + <artifactId>targeting-engine</artifactId> |
| 11 | + <version>4.18.3</version> |
| 12 | +</dependency> |
| 13 | +``` |
| 14 | + |
| 15 | +Requires Java 8+. No runtime dependencies. |
| 16 | + |
| 17 | +## Usage |
| 18 | + |
| 19 | +### 1. Implement `EvaluationContext` |
| 20 | + |
| 21 | +The engine delegates two concerns back to the host application via `EvaluationContext`: |
| 22 | + |
| 23 | +```java |
| 24 | +public class MyEvaluationContext implements EvaluationContext { |
| 25 | + |
| 26 | + // Called for DependencyMatcher and PrerequisitesMatcher |
| 27 | + @Override |
| 28 | + public EvaluationResult evaluate(String matchingKey, String bucketingKey, |
| 29 | + String ruleName, Map<String, Object> attributes) { |
| 30 | + return myEngine.getTreatment(matchingKey, bucketingKey, ruleName, attributes); |
| 31 | + } |
| 32 | + |
| 33 | + // Called for UserDefinedSegmentMatcher |
| 34 | + @Override |
| 35 | + public boolean isInSegment(String segmentName, String key) { |
| 36 | + return mySegmentStorage.isInSegment(segmentName, key); |
| 37 | + } |
| 38 | + |
| 39 | + // Called for RuleBasedSegmentMatcher |
| 40 | + @Override |
| 41 | + public boolean isInRuleBasedSegment(String segmentName, String key, |
| 42 | + String bucketingKey, |
| 43 | + Map<String, Object> attributes) { |
| 44 | + return myRuleBasedSegmentEvaluator.isIn(segmentName, key, bucketingKey, attributes); |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +### 2. Build a `TargetingRule` |
| 50 | + |
| 51 | +`TargetingRule` contains only the fields needed for evaluation. Metadata (name, configurations, flag sets, etc.) belongs in the calling SDK's domain model. |
| 52 | + |
| 53 | +```java |
| 54 | +List<Condition> conditions = List.of( |
| 55 | + new Condition( |
| 56 | + ConditionType.ROLLOUT, |
| 57 | + CombiningMatcher.of(new AllKeysMatcher()), // match everyone |
| 58 | + List.of(new Partition("on", 50), new Partition("off", 50)), |
| 59 | + "default rule" |
| 60 | + ) |
| 61 | +); |
| 62 | + |
| 63 | +TargetingRule rule = new TargetingRule( |
| 64 | + 123456, // seed |
| 65 | + false, // killed |
| 66 | + "off", // defaultTreatment |
| 67 | + conditions, |
| 68 | + 100, // trafficAllocation (0–100) |
| 69 | + 654321, // trafficAllocationSeed |
| 70 | + 2, // algo: 2 = MurmurHash3 (recommended), 1 = legacy |
| 71 | + null // prerequisites (List<Prerequisite> or null) |
| 72 | +); |
| 73 | +``` |
| 74 | + |
| 75 | +### 3. Evaluate |
| 76 | + |
| 77 | +`TargetingEngineImpl` is stateless — create once, reuse across threads. |
| 78 | + |
| 79 | +```java |
| 80 | +TargetingEngine engine = new TargetingEngineImpl(); |
| 81 | + |
| 82 | +EvaluationResult result = engine.evaluate( |
| 83 | + "user-123", // matchingKey |
| 84 | + null, // bucketingKey — null means use matchingKey |
| 85 | + rule, |
| 86 | + Map.of("plan", "pro"), // attributes — may be null |
| 87 | + context |
| 88 | +); |
| 89 | + |
| 90 | +result.treatment // "on" | "off" | ... |
| 91 | +result.label // EvaluationLabels constant (e.g. "default rule") |
| 92 | +``` |
| 93 | + |
| 94 | +## Evaluation Labels |
| 95 | + |
| 96 | +| Label constant | When returned | |
| 97 | +|---|---| |
| 98 | +| `EvaluationLabels.KILLED` | Rule is killed; `defaultTreatment` is returned | |
| 99 | +| `EvaluationLabels.PREREQUISITES_NOT_MET` | A prerequisite flag returned the wrong treatment | |
| 100 | +| `EvaluationLabels.NOT_IN_SPLIT` | User fell outside traffic allocation | |
| 101 | +| `EvaluationLabels.DEFAULT_RULE` | No condition matched; last condition applied | |
| 102 | + |
| 103 | +Additional labels (`DEFINITION_NOT_FOUND`, `EXCEPTION`, `UNSUPPORTED_MATCHER`, `NOT_READY`) are set by the calling SDK, not by the engine. |
| 104 | + |
| 105 | +## Data Model |
| 106 | + |
| 107 | +| Class | Role | |
| 108 | +|---|---| |
| 109 | +| `TargetingRule` | Full rule definition (seed, conditions, traffic allocation, prerequisites) | |
| 110 | +| `Condition` | One targeting condition (`WHITELIST` or `ROLLOUT`) | |
| 111 | +| `Partition` | `(treatment, size%)` pair within a condition | |
| 112 | +| `Prerequisite` | Dependency on another rule's treatment | |
| 113 | +| `CombiningMatcher` | AND-combines a list of `AttributeMatcher`s | |
| 114 | +| `AttributeMatcher` | Extracts one attribute then delegates to a `Matcher` | |
| 115 | + |
| 116 | +## Available Matchers |
| 117 | + |
| 118 | +`AllKeysMatcher`, `WhitelistMatcher`, `EqualToMatcher`, `BetweenMatcher`, `BooleanMatcher`, |
| 119 | +`ContainsAnyOfMatcher`, `StartsWithAnyOfMatcher`, `EndsWithAnyOfMatcher`, `RegularExpressionMatcher`, |
| 120 | +`EqualToSemverMatcher`, `BetweenSemverMatcher`, `GreaterThanOrEqualToSemverMatcher`, |
| 121 | +`LessThanOrEqualToSemverMatcher`, `InListSemverMatcher`, |
| 122 | +`ContainsAllOfSetMatcher`, `ContainsAnyOfSetMatcher`, `EqualToSetMatcher`, `PartOfSetMatcher`, |
| 123 | +`UserDefinedSegmentMatcher`, `RuleBasedSegmentMatcher`, `DependencyMatcher` |
| 124 | + |
| 125 | +## Building |
| 126 | + |
| 127 | +```bash |
| 128 | +mvn -pl :targeting-engine clean install |
| 129 | +``` |
0 commit comments