SoFunction
Updated on 2025-03-05

B station's new generation golang rules engine gengine basic syntax

Preface

  • gengine is a rule engine developed based on golang and AST (abstract syntax tree). The syntax supported by gengine is a custom DSL

  • gengine was authorized by Bilibili() in July 2020 to open source

  • gengine has been used in B station risk control system, traffic delivery system, AB testing, recommendation platform system and other business scenarios.

  • You can also apply genine to any business scenario in golang application that requires rules or metrics to support

Advantages

contrast drools gengine
Execution mode Only sequential mode is supported Supports sequential mode, concurrent mode, mixed mode, and other subdivided execution modes
Difficulty in writing rules High, strongly related to Java Low, custom simple syntax, weakly related to golang
Rule execution performance Low, whether between rules or within rules, they are executed sequentially High, whether it is between rules or within rules, it supports concurrent execution. Users choose the appropriate execution mode based on their needs.

Open source code address

/bilibili/gengine

/bilibili/gengine

grammar

DSL syntax

const rule = `
rule "rulename" "rule-describtion" salience  10
begin
//Rulesend`

As mentioned above, the complete syntax block of gengine DSL consists of the following components:

  • The keyword rule is followed by "rule name" and "rule description". The rule name is necessary, but the rule description is not necessary. When there are multiple rules in a gengine instance, the "rule name" must be unique. Otherwise, when there are multiple rules with the same rule name, only one will exist after compilation.

  • The keyword sales, followed by an integer, represents the priority of the rules, which are not necessary. The larger the number, the higher the priority of the rules; when the user does not explicitly specify the priority, the priority of the rules is unknown. If the priority of multiple rules is the same, then the execution order of the rules with the same priority is unknown when executed.

  • The keywords begin and end are wrapped in the rule body, that is, the specific logic of the rule.

Rule-body grammar

  • The syntax support or execution order of rule bodies is consistent with mainstream computing languages ​​(such as golang, java, C/C++, etc.)

Operations supported by rules

  • Supports four operations between complete values ​​(+), subtraction (-), multiplication (*), and division (/), as well as addition between strings

  • Complete logical operations (&&, ||, !)

  • Comparison operators are supported: equal to (==), not equal to (!=), greater than (>), less than (<), greater than or equal to (>=), less than or equal to (<=)

  • Support +=, -=, *=, /=

  • Support brackets

  • Priority: brackets, non, multiplication and division, addition and subtraction, logical operations (&&,||) are reduced in turn

Basic data types supported by rules

  • string

  • bool

  • int, int8, int16, int32, int64

  • uint, uint8, uint16,uint32, uint64

  • float32, float64

Unsupported special cases

  • It does not support direct processing of nil, but users can define a variable in rule to accept nil, and then define a function to process nil

  • For the convenience of users, the latest version of gengine has built-in isNil() function, which users can use directly to determine whether the data is nil

Syntax supported by rules

  • Complete if .. else if .. else syntax structure, and its nested structure

If you have any other syntax, you can check it in the official document:

/bilibili/gengine/wiki/%E8%AF%AD%E6%B3%95

Use Cases

package test
import (
  "bytes"
  "fmt"
  "/bilibili/gengine/builder"
  "/bilibili/gengine/context"
  "/bilibili/gengine/engine"
  "/sirupsen/logrus"
  "io/ioutil"
  "strconv"
  "strings"
  "testing"
  "time"
)
//Define the structure you want to injecttype User struct {
  Name string
  Age  int64
  Male bool
}
func (u *User)GetNum(i int64) int64 {
  return i
}
func (u *User)Print(s string){
  (s)
}
func (u *User)Say(){
  ("hello world")
}
//Define the rulesconst rule1 = `
rule "name test" "i can"  salience 0
begin
    if 7 == (7){
       = (89767) + 10000000
      ("6666")
    }else{
       = "yyyy"
    }
end
`
func Test_Multi(t *){
  user := &amp;User{
    Name: "Calo",
    Age:  0,
    Male: true,
  }
  dataContext := ()
  //Inject the initialized structure  ("User", user)
  //init rule engine
  ruleBuilder := (dataContext)
  start1 := ().UnixNano()
    //Build rules  err := (rule1) //string(bs)
  end1 := ().UnixNano()
  ("rules num:%d, load rules cost time:%d", len(), end1-start1 )
  if err != nil{
    ("err:%s ", err)
  }else{
    eng := ()
    start := ().UnixNano()
        //Execute the rules    err := (ruleBuilder,true)
    println()
    end := ().UnixNano()
    if err != nil{
      ("execute rule error: %v", err)
    }
    ("execute rule cost %d ns",end-start)
    ("=%d,Name=%s,Male=%t", , , )
  }
}

Example explanation

  • User is a structure that needs to be injected into the gengine; the structure needs to be initialized before injection; the structure needs to be injected as a pointer, otherwise its attribute value cannot be changed in the rule.

  • rule1 is a specific rule defined by a string

  • dataContext is used to accept injected data (structure, method, etc.)

  • ruleBuilder is used to compile rules in string form

  • Engine accepts ruleBuilder and executes the loaded rules in the execution mode selected by the user.

Tips

  • Through the example, we can find that the compilation, construction and execution of rules are asynchronous. Therefore, users can use this feature to update rules without stopping service.

  • It should be noted that compiling and building rules is a CPU-intensive matter. Usually, the compiled and building updates are only compiled and updated when the rules are updated by the user;

  • Gengine has made a lot of optimizations for rule loading and removal internally. All related APIs provided by Gengine pool are thread-safe and high-performance, so we recommend that you use Gengine pool directly.

  • In addition, users can also perform asynchronous syntax detection through ruleBuilder.

If you are interested in design implementation, you can check it through the following address:

///jiaoben/

The above is the detailed content of the basic grammar of the new generation of golang rules engine gengine on B.com. For more information about golang rules engine gengine on B.com, please follow my other related articles!