Project

General

Profile

Cruxdesign » History » Version 2

Shuvam Misra, 10/09/2023 09:14 PM

1 1 Shuvam Misra
# Designing a rules engine for large business applications
2
3
{{>toc}}
4
5
## Background
6
7
We need to have a business rules engine. All the good open source rules engines we are seeing seem to prefer putting the rules in code. This means that a change of rules will require a build and deploy to propagate. Therefore, these products are for a quasi-static rules context. We need to support a more dynamic rules scenario, plus we need to provide a GUI to edit rules. In this scenario, if we are to use the popular rules-as-code category of BRE, we will have to generate code from the admin GUI, and then build a new release with the new rules code. This can be done, but is messy. Elegant people like us  don’t like messy. Unless it’s spelt Messi.
8
9
So we are faced with the prospect of having to design our own rules engine. This rules engine will have absolutely nothing specific to the current project.
10
11
## What is a rule?
12
13
A rule has two “parts”
14
* A pattern specifying which entities the rule will apply to
15 2 Shuvam Misra
* A what-to-do part, or action section, which specifies what we can do or not do with the matching entities
16 1 Shuvam Misra
17
### The pattern
18
19
The first part will have a pattern notation against which we will match entities. For example, these are patterns:
20
21
* Items in the inventory
22
* Textbooks in the inventory
23
* Textbooks more expensive than INR 2,000 in the inventory
24
* Textbooks in stock for longer than 30 days in the inventory
25
* Vendors who owe us more than INR 100,000
26
* Vendors who supplied us more than INR 5,000,000 worth of goods in the last financial year
27
* Vendors who owe us less than INR 50,000 AND who supplied more than INR 2,000,000 worth of goods to us this year
28
* Vendor with the ID “APZ00133”
29
30
The rules engine may allow us to define such patterns, so that we can decide which items the rule will apply on. The last example specifies an explicit vendor instance, by specifying (presumably) its unique ID. Therefore, some rules may be defined for just one object instance. In other words, that rule is not a rule – it’s a codified exception to the general rules.
31
32
One can extend the pattern to have a negation operation. The rule will then apply to all entities which do not match the pattern.
33
34
### The actions
35
36
The second part of the rule is an action or a set of possible actions. Examples are:
37
38
* Discount equals 7% (for old stock items, or expensive items, or all items)
39
* Set credit limit to INR 200,000 (for vendors who owe us more than INR 100,000)
40
* Accept orders without a written PO (for vendors who have supplied goods more than INR 5,000,000 in the last year)
41
* Include in the Diwali Special Sale (for vendors who owe us less than INR 100,000)
42
* Accept orders without a written PO and include in the Christmas Sale (two actions in one rule – why not?)
43
* Use Fedex to ship out the item (for textbooks above a certain value)
44
45
Implementation challenges will be covered later. It’s just concepts at this point.
46
47
## Some refinements
48
49
### A “class of entity” attribute
50
51
A pattern has various attributes. In the items in the inventory examples, the following attributes were seen:
52
 
53
* Type of inventory item: e.g. textbook
54
* Price: e.g. INR 2,000
55
* Age of stock: e.g. 30 days
56
57
It becomes clear that attributes apply to a broad class of entities, but lose meaning across classes. Attributes which make sense to items in the inventory do not have any meaning when applied to vendors. Some attributes, like “full name”, or “date on which the entity was added to the system”, might apply to most items, but such attributes are very few. Therefore, it is perhaps necessary to define one fundamental attribute of each pattern: what broad class of entities does the pattern apply to? In database parlance, this is a mandatory non-NULL field, whose values will be from an enumerated set.
58
59
Another way to view this is to say that rules are partitioned into separate subsets or namespaces based on the value of this primary attribute. Rules which apply to one class do not have any overlap or engagement with rules which apply to another class. A rule can only apply to one class.
60
61
### If-then
62
63
The expressive power of rules will increase if one can specify some rules which will apply only to those entities which match some previous rule. So, Rule A, in addition to specifying actions, will also specify a tag, *e.g*. **“specialvendor”** (for vendors) or **“oldstock”** (for inventory items). Any entity which matches Rule A will be tagged by the tag. Then, there could be other rules B or C which will match only entities which have the **oldstock** tag.
64
65
Once an entity is tagged, it will only match rules which have that tag; it will no longer match any rule which does not have that tag.
66
67
This facility, of rules chaining, allows for more complex rules to be written more easily. For instance, one can define five different patterns by which one will select special vendors. This means that any vendor which matches any of the five criteria will be tagged **specialvendor**. Then later, other rules will specify the actions which can apply to special vendors. Entities which do not get the **specialvendor** tag will “fall through” to the rules which apply to the rest of the entities.
68
69
This chaining facility implements branching in the flow control, somewhat similar to a case statement in C or Go.
70
71
### When many rules match
72
73
When many rules match an entity, there must be a way to break out of the matching loop. The typical solution is to break on first match.
74
75
If this is done, then the ordering of rules becomes important. If we have two rules:
76
* All textbooks
77
* All textbooks with price higher than INR 3,000
78
79
then the order in which matching is attempted will decide whether a book costing more than INR 3,000 will match the first rule or the second.
80
81
A second approach to resolve the ambiguity is to match the more specific rule, i.e. the pattern which carries the larger number of attributes. For instance, in the two rules above, the first rule is more generic, because it matches all textbooks, while the second rule is more specific, because it matches only a subset of textbooks. If there was another rule with both price and weight attributes, that would apply to all matching entities.
82
83
What happens when there are two rules, each of whose patterns has three attributes? If an entity matches all the attributes of both the patterns, which of the rules will apply? In such cases, different attributes may be assigned different weights to break the tie. The pattern with the highest sum of attribute weights overrides the lighter pattern.
84
85
So, a rules engine must decide whether to implement the first-matching-rule algorithm or the more-specific-rule algorithm.
86
A third approach is to not choose which rules match, but make a union set of all the actions of all the matching rules.
87
We will follow the strategy of running through the rules till the end or till an **EXIT** action.
88
89
### Rulesets
90
91
Rules may be grouped into named sets. An action of one rule in one ruleset may trigger the equivalent of a subroutine call to a different ruleset, so that the matching algorithm continues with the rules of the second set. When the second ruleset runs to its end, the matching process returns to the point just after the calling rule in the first ruleset.
92
93
The matching process will always begin with a “main” ruleset, and may just end at the end of this “main” ruleset in some cases. In some specific cases, an entity will match a rule in the “main” ruleset which will specify a CALL action, naming a second ruleset. In that case, the matching process will continue in the second ruleset immediately after completing the rule which had the CALL action. After the called ruleset completes, the matching continues with the rule just after the one which triggered the call.
94
95
### Conditional rules
96
97
A rule may have an action which causes branching to one ruleset if the rule matches, and to another ruleset if it does not match. This is specified in the action section by having a THEN=ruleset1 term and an optional ELSE=ruleset2 term. If such a rule is encountered, then
98
* if the pattern matches the entity, the matching engine will trigger a **CALL** to **ruleset1**.
99
* If there is an **ELSE** term and the pattern does not match the entity, then the engine will trigger a **CALL** to **ruleset2**.
100
101
In both cases, after the **CALL**, the engine will return to the point just after the conditional rule and resume from there.
102
103
### **`RETURN`** and **`EXIT`** actions
104
105
When the matching engine is traversing a ruleset other than **`main`**, and it matches a rule which has the **`RETURN`** action, the engine will immediately exit the ruleset and return to the calling ruleset. If the matching engine encounters the **`EXIT`** action, it will immediately exit from the entire matching process and return to the client which had triggered the matching operation.
106
107
When the matching engine is traversing the main ruleset, **`RETURN`** and **`EXIT`** are synonymous.
108
109
### Values of attributes
110
111
The action of a rule may not just specify an action, it may set a value of an attribute. Thus the rules matching algorithm may not just specify what action is permitted, but may specify the value of a variable too. For instance, for items in inventory, possible actions which are actually attribute-value settings could be:
112
* `shipby=fedex`
113
* `shipby=royalmail`
114
* `HSNcode=9543`
115
116
## Rules engine as flow engine
117
118 2 Shuvam Misra
It is possible to use a rules engine to specify flow, as in task flow or work flow. A task flow basically answers the following question: *“I am entity X of class ABC, and I have just completed operation PQR. What next, for me?”* For example, *"I am vendor PAXX8423, and I have just completed the processing step of initial registration. What's the next step I need to execute?"* Here, the class is "vendor", the entity is PAXX8423, and the operation PQR is "initial registration".
119
120
The basic question which a task flow or work flow answers at every step of a process is: *what next for me?*
121
122 1 Shuvam Misra
To support this, special types of rules will need to be defined. These rules will have the following special features:
123 2 Shuvam Misra
* The repository of flow rules will be separate from the business rules. There is no need for any overlap or mixing between the two sets, therefore they must be disjoint. However, the matching algorithm can be the same.
124
* Features like rule chaining, named rulesets, etc, may all be present, to allow for expressive power.
125
126
All business processes comprise a set of steps. This will reflect in the flow engine. All “flow” rulesets will have two special mandatory attributes:
127
* `process`: which will be one value from an enumerated set of values. Examples of processes could be:
128
  * Account creation
129
  * Asset liquidation
130
  * Identity verification
131
  * Any other multi-step business process
132
* `step`: which will be one value from an enumerated set of values. Examples of `step` could be (taking the example of account creation in a bank):
133
  * Initial personal details
134
  * Identity papers verification
135
  * Reference check
136
  * Initial amount deposit
137
138
These two attributes will be essential to tell the flow engine which process the entity is executing and what is the current step of the process which the entity has just completed, after which it is asking *“what next for me”.*
139
140 1 Shuvam Misra
The flow engine will operate just like a rules engine, and will process rules in its repository,  and will arrive at a set of actions to be performed as the next step. If a single action emerges from the rule matching algorithm, then that step will need to be performed. If multiple actions emerge, then all those actions may be performed in parallel, asynchronously.
141 2 Shuvam Misra
142
If multiple actions emerge from the flow engine, in some cases a special action may be part of the result set: `NEXTSTEP=xyz`. When this appears, it indicates that the entity may execute all the listed actions asynchronously, but must wait for all these asynchronous actions to complete before querying the flow engine again. And when all the actions are complete, it must query the flow engine specifying `xyz` as the value of the `step` attribute. This is a facility to make the flow re-synchronise after breaking into asynchronous concurrent threads.
143
144
## Implementation
145
146 1 Shuvam Misra
A rules engine implementation must include the following:
147 2 Shuvam Misra
* **PATTERN SCHEMA**. A notation to specify the list of valid attributes in the pattern of a rule. This list will be separate for each class of entities. For instance, for items in inventory, the list of attributes may be:
148
  * Price
149
  * Full name
150
  * Age in stock
151
  * Quantity in inventory
152
153
    For vendors, the list could include:
154
  * Amount outstanding
155
  * Total value of business done  in the last financial year
156
157
* **RULE NOTATION**. A notation to specify the pattern and actions of a rule.
158
* **THE MATCHING ENGINE**. Something which will take an entity with all its attributes, apply each rule to it, and follow the trail of rules to come up with a list of actions which will emerge.
159
160 1 Shuvam Misra
So, if these three can be designed and then implemented, the core of a rules engine or a flow engine can be built.
161 2 Shuvam Misra
162
### Representing the schema of patterns
163
164 1 Shuvam Misra
If using JSON, the schema of all valid patterns may be represented in structures of this form:
165 2 Shuvam Misra
166
``` json
167 1 Shuvam Misra
“patternschema”: {
168
    “class”: “inventoryitems”,
169
    “attr”: [{
170
        “name”: “cat”,
171
        “type”: “enum”,
172
        “vals”: [ “textbook”, “notebook”, “stationery”, “refbooks” ]
173
    },{
174
        “name”: “mrp”,
175
        “type”: “float”
176
    },{
177
        “name”: “fullname”,
178
        “type”: “str”,
179
    },{
180
        “name”: “ageinstock”,
181
        “type”: “int”
182
    },{
183
        “name”: “inventoryqty”,
184
        “type”: “int”
185
    }]
186
}
187 2 Shuvam Misra
```
188 1 Shuvam Misra
189 2 Shuvam Misra
In this example, the object `patternschema` is the schema for one category of entities. This schema says that for rules which work on entities of type `inventoryitems`, there are five attributes available which may be used to make patterns. Each attribute has a type. Enum types, integers, floating point numbers, dates and strings are supported. The example above does not have any attribute of type date.
190
191
So, the full schema of the rules engine will be an array of `patternschema` blocks. Initial examples have discussed inventory items and vendors. The `patternschema` block above is for inventory items. If the schema of patterns for vendors needed to be specified, there would be a second `patternschema` with `“class”: “vendors”`
192
193
### Representing the schema of actions
194
195
The schema of the action section of rules is simpler than patterns. Each rule's action section will contain a set of zero or more words, each denoting an action, and zero or more attribute assignments. There is no need for any type specification, etc.
196
* An example of an action word: `invitefordiwali`
197
* An example of an attribute assignment: `discount=7`
198
199
So, the schema of the actions will just specify the valid action names and the attribute names for assignments.
200
201
``` json
202 1 Shuvam Misra
“actionschema”: {
203
    “class”: “inventoryitems”,
204
    “actions”: [ “invitefordiwali”, “allowretailsale”, “assigntotrash” ],
205
    “attribs”: [ “discount”, “shipby” ],
206
    “tags”: [ “specialvendor”, “tryoverseas” ]
207
}
208 2 Shuvam Misra
```
209 1 Shuvam Misra
The schema of actions above indicates that there are three actions, any or all of which may be present in any rule for this class of entities. There are two attributes which may be assigned values by any rule. And there are two tags for this class of entities – if a rule wishes to tag an entity with one or both of these tags, it may do so.
210 2 Shuvam Misra
211
Putting the `patternschema` and `actionschema` blocks together, a better representation for the full schema for a class of entities will be:
212
213
``` json
214
"ruleschema": {
215
    “class”: “inventoryitems”,
216
    “patternschema”: {
217
        “attr”: [{
218
            “name”: “cat”,
219
            “type”: “enum”,
220
            “vals”: [ “textbook”, “notebook”, “stationery”, “refbooks” ]
221
        },{
222
            “name”: “mrp”,
223
            “type”: “float”
224
        },{
225
            “name”: “fullname”,
226
            “type”: “str”,
227
        },{
228
            “name”: “ageinstock”,
229
            “type”: “int”
230
        },{
231
            “name”: “inventoryqty”,
232
            “type”: “int”
233
        }]
234
    }
235
    “actionschema”: {
236
        “actions”: [ “invitefordiwali”, “allowretailsale”, “assigntotrash” ],
237
        “attribs”: [ “discount”, “shipby” ],
238
        “tags”: [ “specialvendor”, “tryoverseas” ]
239
    }
240
}
241
```
242
243
There will need to be one such `ruleschema` block for each class.
244
245
### Representing a pattern
246
247
``` json
248 1 Shuvam Misra
“rulepattern”: {
249
    “pattern”: [{
250
        “attr”: “cat”,
251
        “op”: “eq”,
252
        “val”: “textbook”
253
    },{
254
        “attr”: “mrp”,
255
        “op”: “ge”,
256
        “val”: 2000
257
    },{
258
        “attr”: “ageinstock”,
259
        “op”: “ge”,
260
        “val”: 90
261
    }]
262
}
263 2 Shuvam Misra
```
264 1 Shuvam Misra
265 2 Shuvam Misra
If a rule has this pattern, it will match any entity which falls in the class `inventoryitems` which
266
* is of type textbook
267
* has MRP (max retail price) greater than INR 2000
268
* has been in stock longer than 90 days 
269
270
For attributes which are of type `int` or `float`, the following operators are available for matching:
271
* Greater than or equal to: `ge`
272
* Greater than: `gt`
273
* Less than or equal to: `le`
274
* Less than: `lt`
275
* Equal to: `eq`
276
* Not equal to: `ne`
277
278
For other types, only `eq` and `ne` are available.
279
280
### Representing an action
281
282 1 Shuvam Misra
A rule just has a set of one or more actions. The following are all examples of the action section of rules:
283 2 Shuvam Misra
* `invitefordiwali`
284
* `discount=7`
285
* `shipwithoutpo`
286
* `BTAG=”specialvendor”`
287
* `CALL=”intlbiz”`
288
289
The words which identify actions will automatically be converted to lower-case and stored in the system. For an attribute assignment, the value of the attribute will be everything after the first `=` character till the end of the string, thus supporting multi-word values, *e.g.*
290
* `reprimand=This cannot go on any longer`
291
292
### An entire rule
293
294 1 Shuvam Misra
This is what an entire rule looks like:
295 2 Shuvam Misra
296
``` json
297 1 Shuvam Misra
“rule”: {
298
    “class”: “inventoryitems”,
299
    “ver”: 4,
300
    “rulepattern”: {
301
        “pattern”: [{
302
            “attr”: “cat”,
303
            “op”: “eq”,
304
            “val”: “textbook”
305
        },{
306
            “attr”: “mrp”,
307
            “op”: “ge”,
308
            “val”: 5000
309
        }]
310
    },
311
    “ruleactions”: [
312 2 Shuvam Misra
        “christmassale",
313 1 Shuvam Misra
        “shipby=fedex”
314
    ]
315
}
316 2 Shuvam Misra
```
317 1 Shuvam Misra
318 2 Shuvam Misra
This structure represents one rule. The rule applies to entities of class `inventoryitems`. It has a pattern section which tries to match two attributes and an action section which throws up one action and one assignment.
319
320
An array of such structures is a set of rules, and will be traversed in the order in which the rules appear in the array. Named rulesets will be represented thus:
321
```
322
"ruleset": {
323
    "class": "inventoryitems",
324
    "setname": "overseaspo",
325
    "rules": [{
326
        "ver": 4,
327
        "rulepattern": {
328
            :
329
            :
330
        },
331
        "ruleactions": {
332
            :
333
            :
334
        }
335
    }, {
336
        "ver": 3,
337
        "rulepattern": {
338
            :
339
            :
340
        },
341
        "ruleactions": {
342
            :
343
            :
344
        }
345
    }]
346
}
347
```
348
The example above shows a ruleset named `inventorypo` for class `inventoryitems` which has two rules. This ruleset may be invoked from any other rule with the action `CALL=inventorypo`.
349
350
### The schema manager
351
352 1 Shuvam Misra
The schema for each class of entities may be written by hand using a text editor. JSON or YAML files are easy to write. It is unlikely that the schema of one class will have more than a dozen attributes, which makes the schema short enough to edit or audit by hand. However, a tool to manage and maintain the schema eliminates typos and enforces various types of consistency, and a second-level implementation of a schema manager may also enforce authorisation policies.
353 2 Shuvam Misra
354 1 Shuvam Misra
A schema manager will have the following features:
355 2 Shuvam Misra
* It will allow the user to create new instances of `ruleschema`
356
* It will sharply restrict editing of, and prevent deletion of any `patternschema` block or `actionschema` block if there are rules defined in the rules engine for this class of entities. In other words, schema are editable only as long as there are no rules for the class. The only kind of editing it will permit for “live” schema are
357
  * the addition of additional attributes in a `patternschema` or
358
  * additional attributes, action names or tags in an `actionschema`.
359
* It will ensure that there is no scope for typos when defining the schema.
360
361
### The rule manager
362
363 1 Shuvam Misra
The rule manager will allow a user to manage rules. Core functionality:
364 2 Shuvam Misra
* It will provide a user interface to let the user edit rules.
365
* It will check each rule against the schema for the class, and will not give the user the opportunity to define any rule inconsistent with the schema.
366
* It will allow the user to move a rule up or down in the sequence.
367
* If a rule is being defined with a CALL action, then the rule manager will ensure that a ruleset with that target name exists.
368
* Most important: it will provide a testing facility by which sample entities may be submitted to the rule engine for testing, and the rule manager will display a full trace showing which rules were attempted to match, which rules actually matched, and how the result set of actions, attributes, *etc* grew with each step. This feature will be provided without having to save the rule changes.
369
* Finally, when the editing session is complete and all rulesets need to be saved, it will perform a detailed cross-validation of all rules across each other to ensure consistency. If there is any inconsistency, it will give a very readable explanation of the problem(s) and not permit saving of the updates.
370
371
### The matching engine
372
373 1 Shuvam Misra
The matching engine has a one-line job. It will take a full set of attributes of one entity, apply all the rules which apply to its class, and return with the list of actions, attributes, etc from all the matching rules.
374 2 Shuvam Misra
375 1 Shuvam Misra
The matching engine must support the following set of operations:
376 2 Shuvam Misra
* `doMatch()`: take an entity, pass it through all relevant rules and rulesets, and respond with the set of final results.
377
* `getAttrSet()`: take a class name, pull out from the `patternschema` all the attributes listed against that class, with full details. This is useful to let the caller know what attributes are to be specified when calling `doMatch()`.