Rule Evaluation & Targeting
How the SDK evaluates audience targeting rules and matching conditions
In Bucketing Algorithm, we learned how the SDK uses a "Sorting Hat" approach (hashing) to consistently assign visitors to different variations based on traffic percentages, after they've already qualified for an experiment.
But how does the SDK decide if a visitor qualifies in the first place? What if you only want to run your headline A/B test for visitors from Canada who are using the Chrome browser? How does the SDK check these conditions?
The Problem: Are You on the List?
Imagine you're running a special promotion on your website, but only for visitors who meet specific criteria:
- They must be visiting from Canada.
- They must be using a desktop device.
- They must have visited your pricing page before (maybe indicated by a cookie).
When a visitor arrives, the SDK needs a way to check if they satisfy all these conditions before even considering showing them the promotion (or bucketing them into an experiment variation). How does it perform this check against potentially complex sets of rules?
What is RuleManager? The Club Bouncer
RuleManager? The Club BouncerThink of the RuleManager as the bouncer at an exclusive club. Before anyone gets in (i.e., qualifies for an experiment or feature), the bouncer checks their credentials against the entry rules.
- The Rules: The bouncer has a list of requirements (the targeting rules you set up in your Convert project, like "Location: Canada", "Device: Desktop", "Cookie 'visited_pricing': exists").
- The Visitor's ID: The bouncer looks at the visitor's details (the visitor attributes and location properties provided to the SDK, such as country, device, and cookie values).
- The Check: The bouncer compares the visitor's details against the rules one by one. Do they match?
- The Decision: The bouncer decides: "Yes, this visitor meets all the criteria, let them in!" or "No, they don't meet the criteria, they can't enter."
The RuleManager does exactly this: it takes visitor data and compares it against a set of predefined rules (often called Audience conditions or Segment rules) using various comparison methods (like "equals", "contains", "less than"). It then returns whether the visitor is a "match" or "no match".
How it's Used (The Bouncer Reports to the Manager)
Just like the bouncer doesn't decide the club's overall admission policy but just enforces the rules given, the RuleManager doesn't act alone. You, as the SDK user, rarely interact with it directly.
Instead, the RuleManager is called by the DataManager. Remember the getBucketing flow?
- DataManager checks its cache/store for a prior decision.
- If none exists, it retrieves the experiment/feature details, including the targeting rules.
- Crucially, it asks the
RuleManager: "Hey, does this visitor (with these properties) match these targeting rules?" by callingruleManager.isRuleMatched(visitorData, targetingRules). - The
RuleManager(the bouncer) performs the checks and reports back:true(match),false(no match), or aRuleErrorvalue if data is missing. - If the
RuleManagerreportstrue, then the DataManager proceeds to ask the BucketingManager to assign a variation. Iffalse, the process stops, and the visitor doesn't see the experiment.
So, RuleManager is the specialist that DataManager consults to verify visitor eligibility based on targeting rules.
Under the Hood: How the Rules are Checked
How does the "bouncer" actually read the rules and check the visitor's ID?
1. The Rule Structure (JSON)
The targeting rules you define in Convert are represented inside the SDK as a hierarchical object. This object has a specific structure using OR and AND logic, allowing for complex conditions.
Imagine rules like: "(Country is Canada AND Device is Desktop) OR (User is logged in)".
A simplified JSON representation might look like this:
{
"OR": [
{
"AND": [
{
"OR_WHEN": [
{
"rule_type": "location",
"key": "country",
"matching": {
"match_type": "equals",
"negated": false
},
"value": "Canada"
}
]
},
{
"OR_WHEN": [
{
"rule_type": "visitor",
"key": "device",
"matching": { "match_type": "equals", "negated": false },
"value": "desktop"
}
]
}
]
},
{
"AND": [
{
"OR_WHEN": [
{
"rule_type": "visitor",
"key": "isLoggedIn",
"matching": { "match_type": "equals", "negated": false },
"value": true
}
]
}
]
}
]
}The SDK evaluates this through a 4-level nesting hierarchy:
OR: An array of conditions where at least oneANDblock must be true.AND: An array of conditions where allOR_WHENblocks must be true.OR_WHEN: An array of individual rule checks where at least one must be true. (Often contains just one rule.)RuleElement(the innermost object): Defines a single check (rule_type,key,matchingmethod,value).
2. The Evaluation Process (isRuleMatched)
When DataManager calls ruleManager.isRuleMatched(visitorData, rules), the RuleManager navigates this hierarchy:
- It starts at the top
ORlevel. It processes eachANDblock within theORarray. - For an
ANDblock, it processes eachOR_WHENblock inside. All of these must evaluate totruefor theANDblock to betrue. If anyOR_WHENblock isfalse, theRuleManagerstops processing thisANDblock and moves to the next one in the parentORarray. - For an
OR_WHENblock, it processes each individualRuleElementinside. At least one of these must evaluate totruefor theOR_WHENblock to betrue. If it finds onetruerule, it stops processing thisOR_WHENblock and considers ittrue. - For a single
RuleElement:- It identifies the
rule_type(e.g., 'location', 'visitor', 'cookie'). - It looks up the corresponding value from the visitor data provided (matching by key, with optional case-insensitive comparison).
- It finds the comparison method specified in
matching.match_type(e.g., 'equals', 'contains', 'less'). - It calls that comparison method, passing the visitor's value, the rule's
value, and thenegatedflag. - It considers
matching.negated(e.g., "does not equal"). - It returns
trueorfalsefor this single rule check, or aRuleErrorif data is missing.
- It identifies the
If the RuleManager finds any top-level AND block that evaluates to true (meaning all its conditions passed), the entire isRuleMatched call returns true. If it goes through all the AND blocks and none evaluate to true, it returns false.
3. The Comparison Methods (Comparisons utility)
The actual checking ("equals", "contains", "less than", etc.) happens using helper functions in a Comparisons utility.
These methods take the visitor's data value, the rule's target value, and a negation flag, then perform the check. The comparison is generally case-insensitive for string values. If negation is enabled, the result is inverted (e.g., "does NOT contain").
Available comparison methods include: equals, contains, less, lessEqual, startsWith, endsWith, regexMatches, and others.
The RuleManager uses these comparison methods when processing each individual RuleElement. The comparison processor is pluggable -- you can provide a custom implementation to the constructor.
4. The RuleError Values
When a rule cannot be evaluated because the visitor data is missing or incomplete, the RuleManager returns a RuleError value instead of a boolean. This allows the caller to distinguish between "the visitor doesn't match" (false) and "we can't tell yet because data is missing" (RuleError: NeedMoreData).
The two error states are:
NoDataFound-- No data was provided at all.NeedMoreData-- Some data was provided but the specific key required by the rule is missing.
Simplified Sequence Diagram:
sequenceDiagram
participant DM as DataManager
participant RM as RuleManager
participant Comp as Comparisons
DM->>+RM: isRuleMatched(visitorData, rules)
Note over RM: Start processing rule hierarchy...
RM->>RM: processAND(data, OR[0])
RM->>RM: processORWHEN(data, AND[0])
RM->>RM: processRuleItem(data, OR_WHEN[0]) — Check Country
RM->>Comp: equals(visitorData['country'], 'Canada', false)
Comp-->>RM: true
RM->>RM: processORWHEN(data, AND[1])
RM->>RM: processRuleItem(data, OR_WHEN[0]) — Check Device
RM->>Comp: equals(visitorData['device'], 'desktop', false)
Comp-->>RM: true
Note over RM: AND block is true. OR block is true.
RM-->>-DM: true (Visitor matches)
Implementation Details
The Constructor
The RuleManager is initialized with:
- A reference to the comparison processor (which contains methods like
equals,contains). This is pluggable, allowing customization of the comparison logic. - A
keys_case_sensitivesetting that controls whether rule keys like'country'are matched case-sensitively against visitor data keys. Defaults totrue. - An optional logger for tracing rule evaluation.
The Main Entry Point (isRuleMatched)
This method starts the processing of the rule hierarchy:
- It expects the top level of the rule set to be an
ORarray. - It iterates through the items in the
ORarray (which should beANDblocks). - It calls
processANDfor eachANDblock. - If any call to
processANDreturnstrue, this method immediately returnstrue. - If the loop finishes and the last result was a
RuleError, that error is propagated to the caller. - Otherwise it returns
false.
Processing AND Blocks (processAND)
This helper checks if all conditions within an AND block are met:
- It expects an
ANDarray containingOR_WHENblocks. - It iterates through the
OR_WHENblocks, callingprocessORWHENfor each. - If any call to
processORWHENreturns something other thantrue(eitherfalseor aRuleError), this method immediately returns that value. - If the loop completes (meaning all
OR_WHENblocks weretrue), it returnstrue.
Processing OR_WHEN Blocks (processORWHEN)
This helper checks if at least one condition within an OR_WHEN block is met:
- It expects an
OR_WHENarray containing individualRuleElementobjects. - It iterates through the
RuleElements, callingprocessRuleItemfor each. - If any call to
processRuleItemreturnstrue, this method immediately returnstrue. - If the loop completes with a
RuleErroras the last result, that error is propagated. - Otherwise it returns
false.
Processing a Single Rule (processRuleItem)
This is where the actual comparison happens using the visitor data:
- It extracts the details from the rule object (
match_type,key,value,negated). - It retrieves the corresponding value from the visitor data, matching keys with optional case-insensitivity based on the
keys_case_sensitivesetting. - It calls the appropriate comparison method from the comparison processor (e.g.,
equals,contains), passing the visitor's value, the rule's target value, and the negation flag. - It returns the boolean result of the comparison, or
falseif anything goes wrong.
Conclusion
The RuleManager is the SDK's engine for evaluating complex targeting rules. Like a diligent bouncer, it checks if a visitor's attributes (location, device, cookies, custom properties) match the conditions defined for an experiment, audience, or feature flag.
Key takeaways:
- Why rule evaluation is necessary for targeted experiences.
- The "bouncer" analogy for
RuleManager. - How rules are structured using the 4-level
OR/AND/OR_WHEN/RuleElementhierarchy in JSON. - How
RuleManagerprocesses these rules throughisRuleMatched,processAND,processORWHEN, andprocessRuleItem. - How it uses comparison methods (like
equals,contains,regexMatches) from theComparisonsutility to perform individual checks. - How the
RuleErrorvalues (NoDataFound,NeedMoreData) communicate data issues back to the caller. - That it's primarily used internally by the DataManager to determine visitor eligibility before bucketing.
Now we understand how the SDK gets configuration data (Core), manages individual visitor sessions (Context), handles experiences (Experiences and Variations) and features (Feature Management), stores data and orchestrates decisions (Data Management), allocates traffic fairly (Bucketing Algorithm), and evaluates targeting rules (Rule Evaluation & Targeting).
But how does the SDK actually get the project configuration data (including those rules) from the Convert servers in the first place? And how does it send tracking data back? That's the job of the communicator module.
Let's explore how the SDK interacts with external APIs next: API Management!
Updated about 1 month ago