Grule Rule Engine

repository·master·Indexed 25 days ago

https://github.com/hyperjumptech/grule-rule-engine

A Rule Engine library for the Go programming language inspired by JBOSS Drools. It allows developers to separate business logic from application code using a custom Domain-Specific Language (GRL). The engine supports a Decision Table workflow using DMN 1.3 styled JSON, provides a binary GRB format for optimized rule loading, and utilizes ANTLR4 for parsing GRL rules.

Tokens
26.9K
Snippets
70
Records
114
Agent score
81%

What's inside grule-rule-engine

  1. Core concepts of the Grule Rule Engine

    master

    Grule is a Production Rule System designed to implement an Expert System. It functions by separating business logic from application data.

    Key Components

    • Inference Engine: The core component that matches facts and data against Production Rules (Rules) to infer conclusions and trigger actions.
    • Production Rules: Two-part structures (When/Then) that use first-order logic to reason over knowledge representations.
    • Domain Objects: The data structures that hold the state/facts used by the rules.

    Benefits of using a Rule Engine

    • Declarative Programming: Focuses on "What to do" rather than "How to do it," making rules easier for non-developers (like Business Analysts) to read and verify.
    • Logic and Data Separation: Business logic resides in the rules, while data resides in Domain Objects. This decoupling allows for cleaner architecture.
    • Centralization of Knowledge: Rules act as an executable knowledge base, serving as a single source of truth for business policies.
    • Agility to Change: Because rules are treated as data, they can be updated and rolled out to the knowledge repository without needing to re-build or re-deploy the entire application code.
  2. Important considerations when using JSON facts

    master

    When working with JSON facts in Grule, keep the following behaviors in mind:

    • Decoupled Data: Modifying a JSON fact within the DataContext does not modify the original JSON byte array/string used to create it. Similarly, changes to the original JSON string after it has been added will not affect the facts already in the DataContext.
    • Persistence: If you need modifications made in the then clause to persist back to your original data source, you should parse the JSON into a Go struct first and add that struct to the DataContext instead of using AddJSON.
    • Missing Array Helpers: There is currently no built-in function to easily check array contents, such as a Contains(value) method.
  3. Structure of a GRL Rule

    master

    A rule in the Grule Rule Language (GRL) follows a specific structure consisting of a name, an optional description, an optional salience (priority), a when condition, and a then action block.

    rule <RuleName> <RuleDescription> [salience <priority>] {
        when
            <boolean expression>
        then
            <assignment or operation expression>
    }
    • RuleName: A unique, single-word identifier without whitespace.
    • RuleDescription: A human-readable description enclosed in double quotes.
    • Salience (optional): An integer defining rule priority. Lower values indicate lower priority. It acts as a hint to the engine for conflict resolution. It accepts negative values.
    • Boolean Expression: A predicate evaluated against current facts to determine if the rule should execute.
    • Assignment or Operation Expression: The action(s) to perform if the condition is met. Multiple expressions can be separated by a semicolon (;).
  4. Rules for implementing custom functions in Grule

    master

    When creating custom functions to be called from GRL, you must adhere to these three laws:

    1. Visibility: Functions must be exported (start with a capital letter). Private functions cannot be executed by the engine.
    2. Single Return Value: Functions must return exactly one value type. Returning multiple values is not supported and will cause rule execution to fail.
    3. Numeric Types: Grule treats number literals specifically:
      • Integers are always treated as int64.
      • Reals are always treated as float64. You must define your Go function signatures to match these types exactly.
  5. Understand the core concepts of Grule: Facts, Rules, and KnowledgeBase

    master

    Grule operates on a model where a KnowledgeBase contains sets of rules that are evaluated against Facts.

    • Fact: The basic information or collected data used as the basis for evaluation. Facts can come from various sources like databases, trigger processes, or sales systems. For example, a Purchase Transaction fact might contain Item Name, Quantity, and Price.
    • Rule: A specification of how to evaluate a Fact. A rule consists of a condition (IF) and an action (THEN). If the condition is met by the Fact, the action is executed.
    • Knowledge: A collection of rules that together represent a specific domain of logic (e.g., a set of rules for "calculating the final price of an item").
  6. Call functions and use method chaining in GRL

    master

    GRL allows calling functions that are visible and return zero or one value.

    Key Features:

    • Direct Calls: Fact.FunctionA() == "text"
    • Method Chaining (v1.6.0+): You can chain calls and field access, e.g., Fact.Function().StringField or Fact.Function("arg").ObjField.OtherFunction().
    • Constant Method Calls (v1.6.0+): You can call methods directly on literal constants, e.g., "AString ".Trim().ToUpper().

    Example:

    when
        "AString   ".Trim().ToUpper().HasSuffix("ING")
    then
        Fact.Result = Fact.ReturnStringFunc().Trim().ToLower();
  7. Access and assign to Arrays, Slices, and Maps in GRL

    master

    Since version 1.6.0, GRL supports accessing and assigning values to arrays, slices, and maps within facts.

    Accessing: You can use standard index or key notation. Warning: If an array index is out of bounds, the rule execution will cause a panic.

    Assigning: You can assign values to valid indices or keys.

    // Accessing
    Fact.AnIntArray[1] == 12
    Fact.SubMaps["Key"].AnIntArray[0] == 1000
    
    // Assigning
    Fact.AnIntArray[10] = 12;
    Fact.SubMap["AKey"].AStringArray[1] = "New Value";
    // Example of complex access in a 'when' block
    when 
       Fact.AnIntArray[1] == 12 &&
       Fact.AStringArray[12] != "SomeText" &&
       Fact.SubFacts[1].SubFacts[2].AnIntArray[12] > 100 &&
       Fact.SubMaps["Key"].AnIntArray[0] == 1000
    then
       ...
  8. Understand the Decision Table metamodel

    master

    A Decision Table in Grule is defined by a metamodel that describes the structure of the table, including its items (columns) and decision rows. Each item in the table must define its role, data type, and constraints.

    Key metamodel components for each item include:

    • name: The identifier for the information item.
    • function: Specifies if the item is an input (used for matching conditions) or an output (the result of a rule hit).
    • type: The data type (e.g., int, string, bool, float).
    • label: A human-readable description.
    • allowed_values: Constraints on what values the item can hold (e.g., specific sets or numeric ranges).
    • default_value: The value used if no specific value is provided.
  9. JSON Fact synchronization limitations

    master

    When using JSON facts in Grule, be aware of the following data flow limitations:

    1. One-way initialization: Once a JSON byte array is added to the DataContext, changes to the original byte array will not affect the facts in the engine.
    2. No back-propagation: Changes made to a JSON fact within the GRL then scope will not reflect back to your original JSON string/byte array.

    Recommendation: If you need changes made by the rule engine to persist back to your original data structure, parse your JSON into a Go struct first and add that struct to the DataContext using the standard Add() method instead of AddJSON().

  10. Resolve rule conflicts using salience (priority)

    master

    When multiple rules satisfy their conditions simultaneously, they enter a Conflict Set. To decide which rule to execute first, Grule uses a conflict resolution strategy based on salience (also known as priority or importance).

    • Default Salience: If no salience is specified, the rule's priority is 0.
    • Custom Salience: You can assign a specific priority to a rule. Higher values indicate higher priority.
    • Negative Salience: You can use negative numbers to ensure a rule executes after all other rules (lower priority than the default 0).
    • Tie-breaking: If multiple rules have the same salience, the engine selects the first one it encounters. Because Go maps are unordered, you should not rely on the input order of rules for execution sequence; always use explicit salience for predictable behavior.
    Rule 1 - Priority 1
       IF
       - the Item's Tax is not known AND
       - the Item's Name is "Computer CPU"
       THEN
       - Item's Tax is 10%
    
    Rule 2 - Priority 10
       IF
       - the Item's Tax is not known AND
       - the Item's Name is "Computer Monitor"
       THEN
       - Item's Tax is 7%
  11. Understand the structure of a Rule (GRL)

    master

    A rule in Grule is a fragment of knowledge expressed as a "When-Then" structure. It consists of a name, an optional description, an optional salience (priority), a when block containing conditions, and a then block containing actions.

    Basic syntax structure:

    rule <rule_name> <rule_description>
       <attribute> <value> {
       when
          <conditions>
       then
          <actions>
    }
    rule SpeedUp "When testcar is speeding up we increase the speed." salience 10  {
        when
            TestCar.SpeedUp == true && TestCar.Speed < TestCar.MaxSpeed
        then
            TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement;
            DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed;
    }