-
Couldn't load subscription status.
- Fork 505
Rule Composability / Condition based Facts #336
-
Imagine a hypothetical scenario in which we're using our rules editor to automate a time off system.
We have time off requests which can either be automatically approved, automatically rejected, or left for manager approval.
For our hypothetical scenario we want to reject any time off requests which exceed the employees accrued time off by 16 hours.
We want to automatically approve any requests that won't be rejected where the first day off is more than 3 weeks away.
As it stands today we'd have to do something like this:
[
{
"name": "AutoReject",
"conditions": {
"all": [
{
"fact": "remainingBalance",
"operator": "lessThanInclusive",
"value": -16
}
]
},
"event": {
"type": "reject"
}
},
{
"name": "AutoApprove",
"conditions": {
"all": [
{
"fact": "remainingBalance",
"operator": "greaterThan",
"value": -16
},
{
"fact": "firstDayOff",
"operator": "greaterThanInclusive",
"value": { "fact": "daysFromToday", "params": { "days": 21 } }
}
]
}
"event": {
"type": "approve"
}
}
]The problem is that if our rejection conditions change then we'd need to edit our approval conditions to reflect that, this becomes a maintenance problem as we scale out.
A solution is to extract the common checks into a fact, that produces the following result:
[
{
"name": "AutoReject",
"conditions": {
"all": [
{
"fact": "shouldAutoReject",
"operator": "equal",
"value": true
}
]
},
"event": {
"type": "reject"
}
},
{
"name": "AutoApprove",
"conditions": {
"all": [
{
"fact": "shouldAutoReject",
"operator": "equal",
"value": false
},
{
"fact": "firstDayOff",
"operator": "greaterThanInclusive",
"value": { "fact": "daysFromToday", "params": { "days": 21 } }
}
]
}
"event": {
"type": "approve"
}
}
]This allows us to keep the logic for auto rejecting in one place, and leverage it in both places. However it has also moved the configuration of the auto rejection logic out of the easily transportable json and into code.
My proposal would be to allow the addition of facts that follow the same structure of the rules. For the above fact we'd do this.
{
"name": "shouldAutoReject",
"conditions": {
"all": [
{
"fact": "remainingBalance",
"operator": "lessThanInclusive",
"value": -16
}
]
}
}We could add this to the engine via the engine.addFact method the same way that rules are currently added.
Facts created with conditions like this would return a boolean value based on if the conditions are true or false.
Why Do this instead of XXX
Adding facts in this way for extension and composition in several ways:
- You can effectively create a traditional inheritance hierarchy by doing high priority checks on the value of a parent fact.
- You can write arbitrarily complex composition by choosing how to combine the conditions. For instance you could inherit from disjointed conditions by having 2 facts inside a
anyblock.
Additionally you can just use the mechanism to allow the creation of facts that encapsulate a complex and re-used condition and allow those edits to be done by someone editing the JSON files, and not needing to write code.
This solves for the use case covered by #263
Beta Was this translation helpful? Give feedback.