规则引擎 | 青训营笔记

93 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

本节实践课课讲了规则引擎的设计与实现

1.什么是规则引擎?

根据课程定义:规则引擎是一种嵌入在应用服务中的组件,可以将灵活多变的业务决策从服务代码中分离出来。通过使用预定义的语义模块来编写业务逻辑规则。在执行时接受数据输入、解释业务规则,并做出决策。规则引擎能大大提高系统的灵活性和扩展性。

实际当我们在对复杂的业务进行开发时,程序本身逻辑代码和业务代码经常出现互相嵌套等复杂模式,导致维护成本高,功能拓展性差,而规则引擎就是一个可以降低业务逻辑组件复杂性、降低应用程序维护成本、提高可拓展性的组件

举例而言,对于没有规则引擎的业务开发:

image.png

业务逻辑依靠业务人员与开发人员沟通,然后直接实行代码编写

而有了规则引擎后:

image.png

可以解决很多重复编码的问题,使业务决策与服务本身解耦

2.规则引擎原理

规则引擎内部符合编译原理对代码语句的分析规则,以课程展示的easyengine为例,包含组件:

词法分析器

识别代码语句中各种标识符token并进行错误判断等操作

代码片段:

type Scanner struct {
	source   []rune // 规则表达式字符串
	position int    // 遍历规则表达式过程中的位置
	length   int    // 规则表达式字符串, 用于判断是否扫描结束
	ch       rune   // position 位置对应的字符
}

// read returns the character at the pos of position and advancing
// the scanner. If the scanner is at Eof, read returns -1.
func (scanner *Scanner) read() rune {
	var char rune

	if !scanner.canRead() {
		return eofRune
	}

	char = scanner.source[scanner.position]

	scanner.ch = scanner.peek()

	scanner.position += 1

	return char
}

语法分析

检查语句中各种token组成的语法是否正确

代码片段

func (p *Parser) ParseSyntax() error {
	// '(a + (b > c)' is illegal
	err := p.checkBalance()
	if err != nil {
		return err
	}

	// 'param1 + 100 param2' is illegal
	var lastTok token.Token
	state, err := lastTok.Kind.GetLexerState()
	for p.hasNext() {
		tok := p.next()
		if !state.CanTransitionTo(tok.Kind) {
			return fmt.Errorf("cannot transition token types from %s [%v] to %s [%v]",
				lastTok.Kind.String(), lastTok.Value, tok.Kind.String(), tok.Value)
		}

		state, err = tok.Kind.GetLexerState()
		if err != nil {
			return err
		}

		lastTok = tok
	}

	// 'a + b +' is illegal
	if !state.IsEOF() {
		return errors.New("unexpected end of expression")
	}
	p.Reset()
	return nil
}

语法树构建

代码块以节点方式组建抽象语法树,以便后续以一定规则遍历语法树生成最终代码

node定义:

type Node struct {
	symbol Symbol
	value  interface{}
	tp     TypeFlags

	leftNode, rightNode *Node

	// the operator that will be used to evaluate this node (such as adding [left] to [right] and return the result)
	operator operator

	// ensures that both left and right values are appropriate for this node. Returns an error if they aren't operable.
	typeChecker typeChecker
}

// NewNodeWithPrefixFix The symbol `+` - can represent both unary and binary operators,
// and the priority of the operator is unique and needs to be adjusted on a case-by-case basis
// - In abstract syntax tree, if the left subtree of the node is empty and the right subtree is not empty,
//   it can be judged to be a negative sign. The symbol needs to be corrected
// - If the right subtree of the right subtree is a symbol other than the prefix symbol [+、-],
//	  The node order needs to be corrected