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 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 规则动态编译运行)