The basic conceptual building blocks for Rulette are rule inputs, rules, and rule outputs.
A Rule is a combination of multiple Rule Inputs (a tuple of inputs) mapping to a Rule Output.
To make any decision, we need the parameters that allow us to make that decision. Rulette calls these parameters as rule inputs (or just inputs).
IF X=5 AND Y="Delhi" AND Z=(FROM "Jan 1, 2020" TO "DEC 31, 2020"), THEN "Alpha"
In this example, X, Y, and Z are rule inputs.
Rule inputs have types:
VALUE : VALUE inputs represent discrete values, i.e. parameters which have point values and each value can potentially result in a new output. In the above example X and Y are VALUE type inputs. This type captures individual value of the parameter domain . e.g. If an input to your rule system is "Presidents", then you can define rules for each specific president [Input president = "Obama" => Output "Wall Street Bailout"]
RANGE : If the values of a parameter can be defined in ranges (e.g. prices, time), you can use RANGE type to map ranges of input values onto an output. [Input price = "0-500" => Output "No tax"]. In the above example, Z is a range input, capturing all days of 2020 in a single rule.
Rule inputs have data types. This allows Rulette to evaluate if any of the rules are invalid. STRING, NUMBER (Double), and DATE data types are supported out of the box. Custom data types can be implemented.
Rule Inputs have priorities. Priority defines the order in which inputs are evaluated when Rulette is queried.
Rulette allows us to define rules which contains "Any" as one or more inputs. "Any" (represented by a null value) represent the "Don't care" condition (aka "Others" , "DEFAULT"). We should be able to define a rule for "Any" (aka "Others", "Default") value of an input.
As mentioned above, a rule-input tuple mapped to an output is called a Rule. A rule can contain any number of rule inputs. Many rule input combinations can map to the same rule output.
A Rule System is a collection of rules. Typically all rules in a rule systems pertain to the same use-case (discounts, taxes, multi-tenancy etc) and multiple rule systems should be created to bucket rules for each use-case.
Rule system is the highest abstraction in Rulette, and the one with which clients interact.
Using the domain model
Given this domain model, Rulette allows us to do 3 things :
Model rules - This is the conceptual modelling exercise where we define what the inputs are, what their types, data types and priorities are, etc. Once this model is ready, we can upload a set of rules and Rulette will be able to interpret and use them.
Read/write rules - Rulette allows us to plugin in interfaces via which it creates, reads, updates, and deletes rule modelling and rule data into a storage of our choice. A default MySQL interface is provided out of the box.
Evaluate rules - Once the rule data is loaded into the Rulette engine using the domain model, we can use Rulette to determine which rule is applicable for any given set of input parameters. and hence, what the output should be.
A rule conflict is a situation when Rulette finds two equally applicable rules for a given input. Two rules are considered conflicting when ALL of their rule inputs are conflicting. Rule inputs are said to be conflicting when:
Value input - Conflict if their value is the same.
Range input - Conflict if their ranges are exactly the same or partially overlapping.
A range fully contained inside another range is not considered conflicting. This caveat works very well in defining exceptions to default rules. e.g. a date range input can define a single discount value for the entire year, but we can configure more rules defining different discount rates for specific days. These would not conflict with the overall rule since they are fully contained inside it.
Rulette does not allow defining conflicting rules in a rule system. This opinionated choice helps ensure that bad rules cannot be created while keeping the design by not having to introduce complicated means of conflict resolution (e.g. allowing users to set arbitrary "priority" on rules).
Rulette always returns the best fitting of all defined rules for the given input.
A specific rule (non-"Any" rule) will never be returned if an "Any" value (NULL) is passed in the input.
An "Any" rule may be returned if a specific value is passed in the input but only if a rule with that specific value is not defined.
If passed value for range input fits multiple ranges, the narrower range is considered a better fit.
The evaluation process first goes over all rules (conceptually speaking, the actual algorithm is far better than an all rule scan) and finds out all rules that would fit the given inputs. Then it applies the “best-fit” criterion to all of them and returns the best fitting rule.