规则语言与语法:DRL语法基础

说到规则引擎,DRL(Drools Rule Language)是绕不开的核心语言。我刚开始接触Drools时,第一反应是:这不就是另一种if-else吗?后来踩了几个坑才明白,DRL的威力远不止条件判断那么简单。

DRL语法,说白了就是一套专门描述业务规则的领域特定语言。它让你能用接近自然语言的方式,把复杂的业务逻辑写清楚。我个人习惯把DRL文件看作一个「规则容器」,里面装的是一个个独立的规则单元。

条件(When)与动作(Then)

每个DRL规则都长这样:

rule "规则名称"
    when
        // 条件部分
    then
        // 动作部分
end

when后面跟的是条件,then后面是满足条件后要执行的动作。嗯,这里有个关键点:when里的条件不是简单的布尔表达式,而是模式匹配。

举个例子:

rule "风控-交易金额超限"
    when
        $t : Transaction(amount > 10000)
    then
        System.out.println("交易金额超限:" + $t.getAmount());
        $t.setRiskLevel("HIGH");
end

看到$t了吗?这是绑定变量。它把匹配到的Transaction对象抓出来,后面then里就能直接用。我在项目中遇到过新手把$t写成t,结果编译报错半天找不出原因。记住,DRL里变量名带$是约定俗成的写法,虽然不是强制,但建议遵守。

小技巧:条件部分可以写多个模式,用逗号分隔。比如同时匹配交易对象和用户对象,这在风控场景里很常见。

规则属性(salience、no-loop等)

规则属性是控制规则行为的「开关」。我刚开始用的时候觉得这些属性可有可无,直到有一次线上规则执行顺序乱了,才意识到属性的重要性。

属性名 作用 我的经验
salience 优先级,数值越大越先执行 默认0,我习惯用10的倍数,方便中间插规则
no-loop 防止规则自身循环触发 默认false,建议大部分规则设为true
activation-group 同一组内只执行一条规则 互斥场景必备,比如风控通过/拒绝
date-effective 规则生效日期 促销活动规则常用,省得写定时任务

salience 是最常用的属性。为什么?因为规则引擎的执行顺序不是你写代码的顺序,而是按salience排序。我曾经在项目中遇到一个bug:两条规则都匹配了同一笔交易,结果优先级低的先执行,把状态改了,优先级高的后执行反而报错。后来给每条规则都配了salience,问题就解决了。

rule "高风险交易拦截"
    salience 100
    no-loop true
    when
        $t : Transaction(amount > 50000)
    then
        $t.setStatus("BLOCKED");
end

rule "中风险交易标记"
    salience 50
    no-loop true
    when
        $t : Transaction(amount > 10000)
    then
        $t.setStatus("REVIEW");
end
注意:no-loop这个属性,我建议默认都设为true。除非你明确知道需要循环触发,否则别省这个设置。我曾经因为没设no-loop,导致规则自己触发自己,死循环把CPU跑满了。

类型声明与全局变量

类型声明,说白了就是在DRL文件里定义Java类。为什么要这么做?有时候你不想为了一个简单的POJO单独建一个Java文件,直接在规则文件里声明更方便。

declare RiskResult
    riskLevel : String
    score : int
    reason : String
end

这样声明后,RiskResult就能在规则里直接用了。我个人习惯把风控结果类都声明在规则文件里,因为每个项目的风控维度都不一样,写在Java里反而耦合度高。

全局变量 是另一个实用功能。它允许你在规则里访问外部传入的对象,比如日志记录器、配置信息等。

global org.slf4j.Logger logger
global java.util.Map configMap

rule "记录高风险交易"
    when
        $t : Transaction(amount > 100000)
    then
        logger.warn("高风险交易:{}", $t.getId());
        String threshold = (String) configMap.get("high_risk_threshold");
        System.out.println("当前阈值:" + threshold);
end

这里要注意:全局变量不能在规则里修改值,只能读取。为什么?因为规则引擎是多线程执行的,全局变量修改会导致线程安全问题。我曾经犯过这个错,在规则里改了全局Map的值,结果并发时数据全乱了。

核心要点:
  • DRL的when部分做模式匹配,不是简单的if判断
  • salience控制执行顺序,建议用10的倍数
  • no-loop默认设为true,避免死循环
  • 类型声明适合风控结果类,减少Java代码耦合
  • 全局变量只读不写,保证线程安全

最后说一句,DRL语法看着简单,但真正用好需要积累。我刚开始写规则时,一个文件里塞了几十条规则,后来发现维护起来想死。建议你按业务模块拆分规则文件,每个文件不超过20条规则。这样既清晰又好维护。

DRL规则结构图 规则文件 (.drl) 规则1: 高风险交易拦截 属性: salience=100, no-loop=true 规则2: 中风险交易标记 属性: salience=50, no-loop=true 规则3: 记录高风险交易 属性: salience=10, no-loop=true, activation-group=log when → 条件匹配 → then → 动作执行

这张图展示了一个DRL文件的基本结构。每个规则都包含属性和逻辑体,规则之间通过salience控制执行顺序。实际项目中,你可能会有几十个这样的规则文件,按业务模块组织好。

避坑指南:我曾经在项目里把全局变量当普通变量用,在then里修改了它的值。结果并发测试时,数据全乱了。记住,全局变量只能读,不能写。如果你需要传递数据,用规则间的事实(Fact)传递。

好了,DRL语法基础就聊到这儿。记住三个核心:条件怎么写、动作怎么配、属性怎么设。把这些搞明白,你就能写出靠谱的风控规则了。


公众号:蓝海资料掘金营,微信deep3321