本来这篇文章是计划写流程引擎的,讲一下基于activiti和openwebflow的工作流模块的构建开发过程。但是这周主要研究对象是规则引擎(也叫决策引擎),我也是刚刚接触这部分内容,所以想趁着热气,归纳总结一下。

需要说明的是,Drools和URule都是我刚刚接触的规则引擎,甚至什么是规则引擎我也是才形成一个初步的概念,所以本文如果出现理解偏差还请各位看官多多批评指正。

首先说明什么是规则引擎?在一个系统中,肯定存在非常多的判断规则(例如 age < 15 || age > 65 -> REGECT),实现它最便捷最直接的方式就是使用 if-else 来写,但是随着规则的增加以及需求的变动,代码将变得越来越难阅读和难以管理。

规则引擎是一种嵌入在应用程序中的组件,它可以将业务规则从业务代码中剥离出来,使用预先定义好的语义规范来实现这些剥离出来的业务规则;规则引擎通过接受输入的数据,进行业务规则的评估,并做出业务决策。

因为规则引擎将复杂的业务逻辑从业务代码中剥离出来,所以可以显著降低业务逻辑实现难度;同时,剥离的业务规则使用规则引擎实现,这样可以使多变的业务规则变的可维护,配合规则引擎提供的良好的业务规则设计器,不用编码就可以快速实现复杂的业务规则,同样,即使是完全不懂编程的业务人员,也可以轻松上手使用规则引擎来定义复杂的业务规则。

业务系统运行过程中难免会发生业务规则变化的情形,有了规则引擎,业务规则部分采用的是规则引擎实现,这样在系统正常运行的情况就可以利用规则引擎对业务规则进行修改,从而实现业务规则的随需应便。

本文主要记录Urule-Serve端Urule-Client端分开部署的模式下,以实际业务场景演示URule的安装和使用过程。

服务端: 

引入Maven依赖。Urule有开源版和pro版,由于本项目可能需要部署在生产服务器上,所以此处采用了2.1.3开源版,当然您也可以根据自身的实际情况更换其他版本。

<dependency>
    <groupid>com.bstek.urule</groupid>
    <artifactid>urule-console</artifactid>
    <version>${urule.version}</version>
</dependency>

添加application.yml配置:

urule:
  repository:
    databasetype: mysql
    datasourcename: datasource
server:
  port: 8801

初始化Bean:

/**
 * @author WuGuangNuo
 * @date Created in 2020/3/25 17:36
 */
@Configuration
@ImportResource({"classpath:urule-console-context.xml"})
@PropertySource(value = {"classpath:urule-console-context.properties"})
public class UruleConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourceLoader() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setIgnoreUnresolvablePlaceholders(true);
        configurer.setOrder(1);
        return configurer;
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource datasource() {
        return DataSourceBuilder.create().build();
    }
}

Serverlet:

/**
 * @author WuGuangNuo
 * @date Created in 2020/3/25 17:38
 */
@Component
public class UruleServletRegistration {
    @Bean
    public ServletRegistrationBean registerURuleServlet() {
        return new ServletRegistrationBean(new URuleServlet(), "/urule/*");
    }
}

服务端添加以上内容就可以了,无需添加更多代码。

 客户端:

Maven配置,pom.xml添加和服务端相同的依赖。 application.yml添加如下配置,注意此处的意思是客户端端口8802,监听的服务端为https://api.wuguangnuo.cn。你也可以改为自己本地部署。 

urule:
  resporityServerUrl: https://api.wuguangnuo.cn
  knowledgeUpdateCycle: 1
server:
  port: 8802

初始化Bean:

/**
 * @author WuGuangNuo
 * @date Created in 2020/3/25 17:40
 */
@Configuration
@ImportResource({"classpath:urule-core-context.xml"})
public class RuleConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourceLoader() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setIgnoreUnresolvablePlaceholders(true);
        configurer.setOrder(1);
        return configurer;
    }
}

ServletRegistration:

/**
 * @author WuGuangNuo
 * @date Created in 2020/3/25 17:42
 */
@Component
public class URuleServletRegistration {
    @Bean
    public ServletRegistrationBean registerURuleServlet(){
        return new ServletRegistrationBean(new KnowledgePackageReceiverServlet(),"/knowledgepackagereceiver");
    }
}

进项上述配置后项目就可以启动了。 

URule项目配置:

大家可以打开服务端可视化编辑界面 http://127.0.0.1:8801/urule/frame ,我已将服务端部署在了 https://api.wuguangnuo.cn/urule/frame 这个地址,大家可以在线体验。 


此处以电费的计算为例,演示决策集的配置过程,首先分析业务条件,用户电费计算规则如下表:


为了体现业务多样性和规则可调整性,我们设计一个变量库和一个常量库,用于存储这些数据。变量库放置用户,用电等级和用电价格:

 



我们再创建一个常量库,存储常量数据,折扣系数和原价格。



接下来在页面上编写业务规则:

首先创建一个决策集,命名计算电费.rs.xml,在决策集中添加变量库和常量库。再进行规则的定义和编写: 


如图所示,规则1的含义就是:

如果用户的类型=1,那么用户的金额=用电量X用电价格。

其余规则类似填写。

知识包测试:

在URule当中定义好各种类型的规则文件后,需要将要调用的规则文件通过规则项目的“知识包”节点将文件打包后才可以被业务系统调用,规则包在调用前需要对定义好的知识包进行各种测试。


点击仿真测试,填写好必要的数据,点击测试决策包,回填总金额字段。


查看日志,输出成功。


知识包发布:

右键项目,添加客户端。为了模拟实际调用,这里我使用了内网穿透。 

服务端地址:https://api.wuguangnuo.cn/ 

客户端地址:http://wuguangnuo.wicp.net/



发布并推送知识包:


客户端添加代码:

@RequestMapping("rule")
public String rule(@RequestParam String number, String type) throws IOException {
    String data = "电费2/package1";

    //创建一个KnowledgeSession对象
    KnowledgeService knowledgeService = (KnowledgeService) Utils.getApplicationContext().getBean(KnowledgeService.BEAN_ID);
    KnowledgePackage knowledgePackage = knowledgeService.getKnowledge(data);
    KnowledgeSession session = KnowledgeSessionFactory.newKnowledgeSession(knowledgePackage);

    User user = new User();
    Price price = new Price();
    Level level = new Level();

    user.setNumber(number);
    user.setType(type);

    level.setLevel1("100");
    level.setLevel2("200");

    price.setNoLevel("1");
    price.setLevel1("0.5");
    price.setLevel2("0.6");
    price.setLevel3("0.7");

    session.insert(user);
    session.insert(price);
    session.insert(level);

    session.fireRules();

    return user.getTotal();
}

访问http://wuguangnuo.wicp.net/rule?number=150&type=2 

断点进入了客户端中,并计算得到了正确的结果;反复测试,均能得到期望的结果。