美文网首页
Drools 规则动态编译运行

Drools 规则动态编译运行

作者: 磐不渝 | 来源:发表于2019-11-05 16:46 被阅读0次

    背景

    近期遇到一个规则引擎项目,甲方明确要求用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 规则动态编译运行

    相关文章

      网友评论

          本文标题:Drools 规则动态编译运行

          本文链接:https://www.haomeiwen.com/subject/oycsbctx.html