Drools 规则动态编译运行

背景

近期遇到一个规则引擎项目,甲方明确要求用Drools规则引擎,之前想象中Drools应该应用在风控、预警类场景中。但实际这个项目是个简单场景,只是要一个动态函数调用。

项目规则应用场景

  • 设计期:业务人员可以在线配置一个业务规则,发布后立刻生效。
  • 运行期:客户端根据规则编号去调用规则引擎合规检查,引擎计算完成后将结果返回给客户端。

结合上述场景,实在与Drools的RETE算法不搭边,感觉用Nashorn或Groovy脚本搞定更适合。最终迫于无奈,还是举起了Drools这把牛刀。

我对Drools的期望

  • 能够动态编译运行我生成的规则脚本并计算结果
  • 规则新增、变更后最好能够增量编译,以提升效率

验证过程

Drools (v7.28.0.Final)规则引擎支持使用KieFileSystem内存文件系统方式,编译规则字符串并运行,官方简单的示例代码如下:

public class KieFileSystemExample {

    public void go(PrintStream out) {
        KieServices ks = KieServices.Factory.get();
        KieRepository kr = ks.getRepository();
        KieFileSystem kfs = ks.newKieFileSystem();

        kfs.write("src/main/resources/rules/test.drl", getRule());

        KieBuilder kb = ks.newKieBuilder(kfs);

        kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
        if (kb.getResults().hasMessages(Level.ERROR)) {
            throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
        }

        ReleaseId defaultReleaseId = kr.getDefaultReleaseId();
        KieContainer kContainer = ks.newKieContainer(defaultReleaseId);

        KieSession kSession = kContainer.newKieSession();
        kSession.setGlobal("out", out);

        kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
        kSession.fireAllRules();
    }

    public static void main(String[] args) {
        new KieFileSystemExample().go(System.out);
    }


    private static String getRule() {
        String s = "" +
                   "package org.drools.example.api.kiefilesystem \n\n" +
                   "import org.drools.example.api.kiefilesystem.Message \n\n" +
                   "global java.io.PrintStream out \n\n" +
                   "rule \"rule 1\" when \n" +
                   "    m : Message( ) \n" +
                   "then \n" +
                   "    out.println( m.getName() + \": \" +  m.getText() ); \n" +
                   "end \n" +
                   "rule \"rule 2\" when \n" +
                   "    Message( text == \"Hello, HAL. Do you read me, HAL?\" ) \n" +
                   "then \n" +
                   "    insert( new Message(\"HAL\", \"Dave. I read you.\" ) ); \n" +
                   "end";

        return s;
    }

}

上述示例代码简洁易懂,但示例中没有提供模块化的方式写法。每次均为全量编译覆盖。

那么动态更新,增量编译该如何处理?

经过验证,可以利用drools的多模块特性,给每个模块绑定不同的ReleaseId,不同模块创建不同的KieContainer即可。在当前项目中由于需要根据规则编号调用规则,因此每个规则就是一个独立模块,可以独立编译、运行。
简单封装了一个DynamicRuleService动态规则编译运行的类,供大家参考。

示例代码如下:

  • 运行入口
public class DynamicRuleDemo {

    public static void main(String[] args) {
        
        
        System.out.println("************run rule 11111");
        DynamicRuleService.getInstance().buildRuleContent( "rule-123123123", "1.0.0",getRule11111111()); // 如果规则有变化,则调用此方法编译规则。 
        KieSession kSession = DynamicRuleService.getInstance().getRuleSession("rule-123123123", "1.0.0");
        kSession.setGlobal("out", System.out);
        kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
        kSession.fireAllRules();
        
        
        System.out.println("************run rule 22222");
        DynamicRuleService.getInstance().buildRuleContent("rule-456456456", "1.0.0", getRule2222222()); // 如果规则有变化,则调用此方法编译规则。 
        KieSession kSession2 = DynamicRuleService.getInstance().getRuleSession("rule-456456456", "1.0.0");
        kSession2.setGlobal("out", System.out);
        kSession2.insert(new Message("Dave", "Hello, Dog.  Do you read me, Dog?"));
        kSession2.fireAllRules();
        
        
        System.out.println("************run rule 11111 again ");
        kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
        kSession.fireAllRules();
        
        System.out.println("************run rule 22222 again ");
        kSession2.insert(new Message("Dave", "Hello, Dog.  Do you read me, Dog?"));
        kSession2.fireAllRules();
    }


    private static String getRule11111111() {
        String s = "" +
                   "package org.kaw.demo.rule.demo \n\n" +
                   "import org.kaw.demo.rule.demo.DynamicRuleDemo.Message \n\n" +
                   "global java.io.PrintStream out \n\n" +
                   "rule \"rule 1\" when \n" +
                   "    m : Message( ) \n" +
                   "then \n" +
                   "    out.println( m.getName() + \": \" +  m.getText() ); \n" +
                   "end \n" +
                   "rule \"rule 2\" when \n" +
                   "    Message( text == \"Hello, HAL. Do you read me, HAL?\" ) \n" +
                   "then \n" +
                   "    insert( new Message(\"HAL\", \"Dave. I read you.\" ) ); \n" +
                   "end";

        return s;
    }

    private static String getRule2222222() {
        String s = "" +
                   "package org.kaw.demo.rule.demo \n\n" +
                   "import org.kaw.demo.rule.demo.DynamicRuleDemo.Message \n\n" +
                   "global java.io.PrintStream out \n\n" +
                   "rule \"rule 0\" when \n" +
                   "    m : Message( ) \n" +
                   "then \n" +
                   "    out.println( m.getName() + \": \" +  m.getText() ); \n" +
                   "end \n" +
                   "rule \"rule 3\" when \n" +
                   "    Message( text == \"Hello, Dog.  Do you read me, Dog?\" ) \n" +
                   "then \n" +
                   "    insert( new Message(\"Dog\", \"Ignore any message.\" ) ); \n" +
                   "end";

        return s;
    }
    
    public static class Message {
        private String name;
        private String text;
        
        public Message(String name, String text) {
            this.text = text;
            this.name = name;
        }
        
        public String getText() {
            return text;
        }
        
        public void setText(String text) {
            this.text = text;
        }
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
    }

    
}
  • 动态规则服务类
public class DynamicRuleService {

    public static final String RESOURCE_PATH = "src/main/resources/";
    public static final String DEFAULT_GROUP_ID = "com.kaw.rule";

    /**
     * 函数调用型规则中,每个规则文件的 ruleCode+ version 对应一个 KieContainer
     * 后续需要考虑缓存容量
     */
    protected Map<ReleaseId, KieContainer> containerMapper = new ConcurrentHashMap<>();

    private static DynamicRuleService INSTANCE = new DynamicRuleService();

    private DynamicRuleService() {
    }

    public static DynamicRuleService getInstance() {
        return INSTANCE;
    }

    /**
     * 获取规则会话,用来调用规则
     * @param ruleCode
     * @param version
     * @return
     */
    public KieSession getRuleSession(String ruleCode, String version) {
        return getRuleSession(DEFAULT_GROUP_ID, ruleCode, version);
    }
    
    /**
     * 获取规则会话,用来调用规则
     * @param ruleCode
     * @param version
     * @return
     */
    public KieSession getRuleSession(String groupId, String ruleCode, String version) {
        KieContainer container = getRuleContainer(groupId, ruleCode, version);
        return container.newKieSession();
    }

    /**
     * 编译规则内容;会覆盖上次编译
     * 
     * @param content
     */
    public void buildRuleContent(String ruleCode, String version, String content) {
        buildRuleContent(DEFAULT_GROUP_ID, ruleCode, version, content);
    }
    /**
     * 编译规则内容;会覆盖上次编译
     * 
     * @param content
     */
    public void buildRuleContent(String groupId ,String ruleCode, String version, String content) {
        KieServices kService = KieServices.Factory.get();
        ReleaseId releaseId = kService.newReleaseId(groupId, ruleCode, version);
        ;
        KieFileSystem fileSystem = kService.newKieFileSystem();
        fileSystem.generateAndWritePomXML(releaseId);
        
        String path = DynamicRuleService.RESOURCE_PATH + releaseId.getGroupId() + "/" + releaseId.getArtifactId() + "-"
                + releaseId.getVersion() + ".drl";
        System.out.println("### build rule content : " + path);
        fileSystem.write(path, content);
        KieBuilder kbuilder = kService.newKieBuilder(fileSystem);
        kbuilder.buildAll();
        
        if (kbuilder.getResults().hasMessages(Level.ERROR)) {
            throw new RuntimeException("Rule Build Errors:\n" + kbuilder.getResults().toString());
        }
        
    }

    private ReentrantLock initContainerLock = new ReentrantLock();
    private KieContainer getRuleContainer(String groupId, String ruleCode, String version) {
        KieServices kService = KieServices.Factory.get();
        ReleaseId releaseId = kService.newReleaseId(groupId, ruleCode, version);

        KieContainer kContainer = containerMapper.get(releaseId);

        if (kContainer == null) {
            initContainerLock.lock();
            try {
                if (kContainer == null) {
                    kContainer = kService.newKieContainer(ruleCode, releaseId);
                    containerMapper.put(releaseId, kContainer);
                }
            } finally {
                initContainerLock.unlock();
            }
        }
        return kContainer;
    }
}
  • 运行结果
************run rule 11111
### build rule content : src/main/resources/com.kaw.rule/rule-123123123-1.0.0.drl
ave: Hello, HAL. Do you read me, HAL?
HAL: Dave. I read you.
************run rule 22222
### build rule content : src/main/resources/com.kaw.rule/rule-456456456-1.0.0.drl
Dave: Hello, Dog.  Do you read me, Dog?
Dog: Ignore any message.
************run rule 11111 again 
Dave: Hello, HAL. Do you read me, HAL?
HAL: Dave. I read you.
************run rule 22222 again 
Dave: Hello, Dog.  Do you read me, Dog?
Dog: Ignore any message.

验证结果

不出所料,Drools符合我的期望,能够动态增量编译运行规则。有类似场景的同学们,可以参考上述示例代码,结合规则文件的管理功能,就能实现类似的需求。

转载本文需注明出处:Drools 规则动态编译运行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343