markdown Copy code
REX is a rules engine designed to process complex conditions and actions using a structured JSON format for rule definitions. It allows for defining rules, conditions, and actions that are compiled into bytecode by the REX Compiler, then executed by the REX Engine. REX is designed to be used in conjunction with a key/value store such as Redis or NATS, where REX subscribes to and receives updates from the key/value store, evaluates the updated value, then updates and publishes applicable results back to the store.
- Define rules using JSON
- Support for various data types and comparison operations
- Logical and control flow instructions
- Action execution based on rules
- New: Scripting capabilities using Otto (JavaScript engine)
- Go 1.20 or higher
- Redis server running on localhost:6379 (for default configuration)
Clone the repository:
git clone https://github.com/rgehrsitz/rex.gitNavigate to the project directory:
cd rexThe REX repository includes four main executables: rexc, rexd, redis_setup, and rule_gen. Below are the details on how to build, run, and understand the purpose of each executable.
Purpose: The rexc executable is the compiler that translates rules defined in JSON format into bytecode instructions that the runtime engine can execute.
How to Build:
go build ./cmd/rexcHow to Run:
./rexc -rules <path_to_rules.json> [-loglevel <level>] [-logoutput <output>]Command-line options:
-rules: (Required) Path to the input JSON file containing the rules.-loglevel: (Optional) Set log level. Valid values are panic, fatal, error, warn, info, debug, trace. Default is "info".-logoutput: (Optional) Set log output. Valid values are console or file. Default is "console".
Example:
./rexc -rules examples/rules2.json -loglevel debug -logoutput filePurpose: The rexd executable is the runtime engine that processes the bytecode generated by rexc. It uses a Redis store for managing and updating facts in real-time based on the rules.
How to Build:
go build ./cmd/rexdHow to Run:
./rexd -config <path_to_config.json>Command-line options:
-config: (Optional) Path to the configuration file. If not specified, rexd will look for a file namedrex_config.jsonin the current directory,$HOME/.rex, and/etc/rex.
Configuration File (rex_config.json): The configuration file is in JSON format and supports the following options:
{
"bytecode_file": "output.bytecode",
"logging": {
"level": "debug",
"destination": "console",
"timeFormat": "unixnano"
},
"redis": {
"address": "localhost:6379",
"password": "",
"database": 0,
"channels": ["weather", "system", "network", "energy", "water"]
},
"engine": {
"priority_threshold": 1
}
}Example:
./rexd -config cmd/rexd/rex_config.jsonPurpose: The redis_setup executable initializes the Redis database with default values necessary for some testing of the REX system. It also provides a CLI for modifying values during debugging.
How to Build:
go build ./tools/redis_setupHow to Run:
./redis_setupThis tool doesn't have any command-line options. It connects to Redis at localhost:6379 by default.
After running, it provides an interactive CLI with the following command:
set <group:key> <value>Example:
./redis_setup
Enter command (set <group:key> <value> or exit): set weather:temperature 30.5Purpose: The rule_gen executable generates a large number of random rules in JSON format, which can be used for testing and benchmarking the REX system.
How to Build:
go build ./tools/rule_genHow to Run:
./rule_gen [-rules <number_of_rules>] [-output <output_file.json>]Command-line options:
-rules: (Optional) Number of rules to generate. Default is 1000.-output: (Optional) Output file name. Default is "generated_ruleset.json".
Example:
./rule_gen -rules 1000 -output generated_ruleset.json- Define your rules in a JSON file.
- Use
rexcto compile the rules into bytecode. - Set up your Redis instance and initialize it with
redis_setupif needed. - Run
rexdwith the compiled bytecode to start the rules engine. - The engine will listen for updates from Redis, evaluate rules, and perform actions accordingly.
cmd/rexc: Main application entry point for the compilercmd/rexd: Main application entry point for the runtime enginepkg/compiler: Contains the bytecode compiler and related functionspkg/runtime: Contains the runtime engine and related functionspkg/scripting: Contains the scripting engine and related functions using Ottopkg/store: Contains the Redis store implementationpkg/logging: Contains logging utilitiestools/redis_setup: Redis setup and CLI tooltools/rule_gen: Random rule generation tool
Rules are defined in a JSON format. Each rule consists of conditions and actions. Here's an example:
{
"rules": [
{
"name": "rule-1",
"conditions": {
"all": [
{ "fact": "weather:temperature", "operator": "GT", "value": 30 },
{ "fact": "weather:humidity", "operator": "LT", "value": 40.01 }
]
},
"actions": [
{
"type": "updateStore",
"target": "weather:temperature_warning",
"value": "high"
}
]
}
]
}The rules are defined in a JSON file with the following structure:
- rules: an array of rule objects
A rule object has the following properties:
- name: a unique string identifying the rule
- priority: optional integer indicating the rule's priority (default: 10)
- conditions: an object containing a single property:
- ANY or ALL: an array of condition groups
- actions: an array of action objects
A condition group is an object containing:
- conditions: an array of condition objects.
- operator: a string indicating the logical operator (ANY or ALL).
A condition object has the following properties:
-
fact: a string identifying the fact to evaluate. Based on the way Redis works, the recommendation is 'channel ' for the naming of facts.
-
operator: a string indicating the comparison operator (EQ, NEQ, LT, LTE, GT, GTE, CONTAINS, NOT_CONTAINS).
-
value: the value to compare against.
**All condition objects not part of a grouping MUST be defined prior to any nested condition groups.
**The characters in a string must NOT include a colon ':' due to how Redis parses channels/keys.
An action object has the following properties:
- type: a string indicating the action type ("updateStore" or "sendMessage") (sendMessage is not yet implemented).
- fact: a string identifying the fact to update or send. Based on the way Redis works, the recommendation is 'channel ' for the naming of facts.
- value: the value to update or send.
- customProperty: an optional object containing custom properties for the action.
REX supports scripting using the Otto JavaScript engine. Scripts can be defined and executed as part of the rule actions. This allows for more complex logic and calculations.
Scripts are defined in the Scripts section of a rule and can be referenced in actions. Here's an example:
{
"rules": [
{
"name": "rule-1",
"conditions": {
"all": [{ "fact": "temperature", "operator": "GT", "value": 30 }]
},
"actions": [
{
"type": "updateStore",
"target": "heat_index",
"value": "{calculate_heat_index}"
}
],
"scripts": {
"calculate_heat_index": {
"params": ["temperature", "humidity"],
"body": "return temperature * 1.8 + 32 + (humidity / 100) * 10;"
}
}
}
]
}{
"rules": [
{
"name": "rule-1",
"priority": 10,
"conditions": {
"all": [
{
"fact": "temperature",
"operator": "GT",
"value": 30.0
},
{
"fact": "humidity",
"operator": "LT",
"value": 60
},
{
"any": [
{
"fact": "pressure",
"operator": "LT",
"value": 1010
},
{
"fact": "flow_rate",
"operator": "GT",
"value": 5.0
}
]
}
]
},
"actions": [
{
"type": "updateStore",
"target": "temperature_status",
"value": true
},
{
"type": "updateStore",
"target": "heat_index",
"value": "{calculate_heat_index}"
}
],
"scripts": {
"calculate_heat_index": {
"params": ["temperature", "humidity"],
"body": "return temperature * 1.8 + 32 + (humidity / 100) * 10;"
}
}
},
{
"name": "rule-2",
"priority": 5,
"conditions": {
"all": [
{
"any": [
{
"fact": "pressure",
"operator": "EQ",
"value": 1013
},
{
"fact": "flow_rate",
"operator": "GTE",
"value": 5.0
}
]
},
{
"any": [
{
"fact": "temperature",
"operator": "EQ",
"value": 72
},
{
"fact": "flow_rate",
"operator": "LT",
"value": 5.0
}
]
}
]
},
"actions": [
{
"type": "sendMessage",
"target": "alert-service",
"value": "Alert - Pressure or flow rate exceeded limits!"
}
]
}
]
}Actions will be executed in the order they are defined in the rule.
Facts are strings. Values can be strings surrounded by quotation marks (e.g. "fact_a"), bools (e.g. true or false), or numbers with or without decimal points (e.g. 30.01, 30, -12.123).
Due to concurrent evaluations and other factors, no guarantees can be made regarding how priority ties are resolved. The engine will do its best to resolve all higher priority rules before lower ones, but no precedence can be guaranteed beyond that.
To run the tests:
go test ./...This project is licensed under the MIT License - see the LICENSE file for details.