規(guī)則引擎是嵌入在應(yīng)用程序中的組件遇八,實現(xiàn)了決策邏輯和業(yè)務(wù)系統(tǒng)的分離功能是掰。在現(xiàn)實業(yè)務(wù)場景中虑鼎,決策邏輯的復(fù)雜性和可變性,使得決策引擎的應(yīng)用越來越多,把決策邏輯單獨分離出來也顯得越來越重要了炫彩。
目前市場上常用的規(guī)則引擎有Ilog JRules匾七,Drools,Jess媒楼,Visual Rules等乐尊。Ilog JRules 是最有名的商用BRMS戚丸;Drools 是最活躍的開源規(guī)則引擎划址;Jess 是Clips的java實現(xiàn),就如JRuby之于Ruby限府,是AI系的代表夺颤; Visual Rules(旗正規(guī)則引擎)國內(nèi)商業(yè)規(guī)則引擎品牌。但是這些規(guī)則引擎都需要生成大量的bean類和Judgment類胁勺,在實現(xiàn)規(guī)則判斷的時候世澜,需要編寫大量的java代碼,或者使用rete規(guī)范署穗,另外編寫腳本寥裂。然后我們實際編程中,這些bean的數(shù)據(jù)大多數(shù)存于數(shù)據(jù)庫中案疲,規(guī)則引擎的判斷實際上是SQL腳本運行的一部分封恰。
因此在這里介紹一款可以使用SQL腳本來定義規(guī)則的中間件 -- RuleEngine。RuleEngine已經(jīng)登記在Maven中的中央庫中了褐啡,我們可以直接在POM.xml文件中包含就可以了诺舔。
<dependency>
? ? ? ? <groupId>com.github.hale-lee</groupId>
? ? ? ? <artifactId>RuleEngine</artifactId>
? ? ? ? <version>0.1.0</version>
? ? </dependency>
? 使用前,需要先配RuleEngine的配置文件ruleEngine.properties备畦,RuleEngine支持3種規(guī)則定義方式低飒,分別是1,數(shù)據(jù)庫table配置懂盐;2褥赊,xml文件配置;3莉恼,Drools的drl文件方式拌喉。
在ruleEngine.properties中設(shè)置rule.reader字段即可,下面分別說明:
1类垫, 數(shù)據(jù)庫table配置方式司光,在ruleEngine.properties中,設(shè)置rule.reader = database
此時需要配置下面的信息
a)? ? db.rule.table字段悉患,設(shè)置db.rule.table=tl_rule_define(表名)残家。此表結(jié)構(gòu)的定義在https://github.com/Hale-Lee/RuleEngine/tree/dev/referenc中有定義(有oralce和mysql的2種方式)。
b)? ? db.accesser數(shù)據(jù)連接方式售躁,RuleEngine提供了直接的jdbc連接坞淮,Druid連接池茴晋,Spring框架連接3種方式。 如果使用jdbc鏈接或者是Druid鏈接回窘,那么需要設(shè)置db.accesser=tech.kiwa.engine.utility.DirectDBAccesser(設(shè)置DirectDBAccesser的UseDruid可以區(qū)別是否使用Druid連接池诺擅,默認是true使用)。如果直接使用Spring框架的連接啡直,那么需要設(shè)置db.accesser= tech.kiwa.engine.utility. SpringDBAccesser烁涌。
RuleEngine也提供DBAccesser的接口,我們可以通過實現(xiàn)DBAccessor的接口的方法來自己獲得自己的連接酒觅。
c)? ? 如果使用jdbc鏈接或者是Druid鏈接撮执,那么需要配置jdbc屬性,或者是Druid的連接池參數(shù)舷丹,RuleEngine可以獨立配置連接池參數(shù)抒钱,也可以直接使用項目中現(xiàn)有的連接參數(shù)。
典型的配置文件的方式如下(使用Druid配置):
#數(shù)據(jù)驅(qū)動
jdbc.driver=com.mysql.cj.jdbc.Driver
#jdbc.driver=oracle.jdbc.driver.OracleDriver
#數(shù)據(jù)庫連接
jdbc.url=jdbc:mysql://127.0.0.1:3306/hosp?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true
#數(shù)據(jù)庫用戶名
jdbc.username=oracle
jdbc.password=user
#規(guī)則定義的表
db.rule.table=TL_RULE_DEFINE
db.accesser=tech.kiwa.engine.utility.DirectDBAccesser
rule.reader=database
數(shù)據(jù)庫表結(jié)構(gòu)的定義如下:
-- Table structure for TL_RULE_DEFINE
?-- ----------------------------
?DROP TABLE "TL_RULE_DEFINE";
?CREATE TABLE "TL_RULE_DEFINE" (
?"ITEM_NO" NVARCHAR2(32) NOT NULL ,
?"CONTENT" NVARCHAR2(256) NULL ,
?"EXE_SQL" NVARCHAR2(512) NULL ,
?"EXE_CLASS" NVARCHAR2(128) NULL ,
?"PARAM_NAME" NVARCHAR2(128) NULL ,
?"PARAM_TYPE" NVARCHAR2(128) NULL ,
?"COMPARISON_CODE" NVARCHAR2(32) NULL ,
?"COMPARISON_VALUE" NVARCHAR2(64) NULL ,
?"BASELINE" NVARCHAR2(64) NULL ,
?"RESULT" NVARCHAR2(6) NULL ,
?"PRIORITY" NVARCHAR2(32) NULL ,
?"CONTINUE_FLAG" NVARCHAR2(2) NULL ,
?"PARENT_ITEM_NO" NVARCHAR2(2) NULL ,
?"PARENT_EXPRESS" NVARCHAR2(256) NULL ,
?"EXECUTOR" NVARCHAR2(64) NULL ,
?"REMARK" NVARCHAR2(64) NULL ,
?"COMMENTS" NVARCHAR2(64) NULL ,
?"ENABLE_FLAG" NVARCHAR2(2) NULL ,
?"CREATE_TIME" TIMESTAMP(6)? NULL ,
?"UPDATE_TIME" TIMESTAMP(6)? NULL
?)
?LOGGING
?NOCOMPRESS
?NOCACHE;
?COMMENT ON TABLE "TL_RULE_DEFINE" IS '規(guī)則引擎定義表';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."ITEM_NO" IS '主key';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."CONTENT" IS '中文的內(nèi)容說明';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."EXE_SQL" IS '執(zhí)行的SQL語句';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."EXE_CLASS" IS '執(zhí)行檢查的java類名颜凯, 與exe_sql二者只填寫一項';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."PARAM_NAME" IS 'SQL語句的參數(shù),多個參數(shù)用,分割谋币,讀值時需要完成和繼承DefaultCustomerCheck類。';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."PARAM_TYPE" IS 'exe_sql或者exe_class的參數(shù)類型症概,多個類型用逗號(,)分割蕾额,與param_name需一一對應(yīng)。';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."COMPARISON_CODE" IS '01: = 穴豫,? 02: > 凡简, 03 : < , 04 != 精肃, 05 >= 秤涩, 06: <= , 07 include 司抱, 08 exclude 筐眷, 09: included by 10: excluded by? 11: equal , 12 : not equal 13: euqalIngoreCase 15: matches 16: NOT MATCHES';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."COMPARISON_VALUE" IS '=,>,<,>=,<=, !=, include, exclude等內(nèi)容。';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."BASELINE" IS '參數(shù)值习柠,比較目標值';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."RESULT" IS '1 - 通過? 2 - 關(guān)注 3 -拒絕? 邏輯運算滿足目標值的時候讀取改內(nèi)容匀谣。';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."PRIORITY" IS '執(zhí)行的優(yōu)先順序,值大的優(yōu)先執(zhí)行.';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."CONTINUE_FLAG" IS '是否繼續(xù)執(zhí)行下一條资溃,如果某條規(guī)則滿足中斷的話武翎,那么就設(shè)置為 2. 1 -- 繼續(xù)? 2 -- 中斷';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."PARENT_ITEM_NO" IS '如果是子規(guī)則,那么需要填寫父規(guī)則的item_no';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."PARENT_EXPRESS" IS '同一PARENT_ITEM的各個ITEM的運算表達式溶锭。 ( A AND B OR C)';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."EXECUTOR" IS '結(jié)果執(zhí)行后的被執(zhí)行體宝恶,從AbstractCommand中繼承下來。';
?COMMENT ON COLUMN "TL_RULE_DEFINE"."ENABLE_FLAG" IS '是否使用 1 - 有效? 2 - 失效';
?-- ----------------------------
?-- Checks structure for table TL_RULE_DEFINE
?-- ----------------------------
?ALTER TABLE "TL_RULE_DEFINE" ADD CHECK ("ITEM_NO" IS NOT NULL);
其說明可以參考:
2, xml文件配置垫毙,xml文件配置的方式也需要在ruleEngine.properties中配置引擎的讀寫方式霹疫。僅僅配置兩項內(nèi)容即可。
rule.reader=xml
#指定規(guī)則文件的文件名综芥,RuleEngine會從classpath中搜索該文件丽蝎。
xml.rule.filename=ruleconfig.xml
XML文件的寫法與table類似,典型的xml文件格式為:
<?xml version="1.0" encoding="UTF-8"?>
<rules >
? <organization>
? ? ? <url>www.keaitupian.com</url>
? </organization>
? ? <description>
? ? ? ? Configuration for the rule list which stores the rule information in-memory and executed by rule engine service.
? ? </description>
? ? <rule id="totallist" exe_class="" method="" parent="">
? ? ? ? <property name="content" value="客戶身份證號碼規(guī)則"/>
? ? ? ? <property name="result" value="RESULT.REJECTED" desc="拒絕"/>
? ? ? ? <property name="continue_flag" value="1"/>
? ? ? ? <property name="group_express" value="(blacklist || graylist)"/>
? ? ? <property name="priority" value="10"/>
? ? </rule>
? ? <rule id="blacklist" parent="totallist">
? ? ? ? <property name="content" value="客戶身份證號碼命中內(nèi)部黑名單"/>
? ? ? ? <property name="exe_sql" value="select count(1) from customer_black_list where certificate_type >=1 and customer_no = ? and is_black = 1"/>
? ? ? ? <property name="param" value="CUSTOMER_NO" type="java.lang.String" desc="客戶編號"/>
? ? ? ? <property name="comparison_code" value="02"/>
? ? ? ? <property name="comparison_value" value=">"/>
? ? ? ? <property name="baseline" value="0"/>
? ? ? ? <property name="baseline_desc" value="客戶的身份證號碼在黑名單表個數(shù)中大于0"/>
? ? </rule>
? ? <rule id="graylist" exe_class="" method="" parent="totallist">
? ? ? ? <property name="content" value="客戶身份證號碼命中內(nèi)部灰名單"/>
? ? ? ? <property name="exe_sql" value="select count(1) from customer_black_list where certificate_type =1 and customer_no = ? and is_gray = 1"/>
? ? ? ? <property name="param" value="CUSTOMER_NO" type="java.lang.String" desc="客戶編號"/>
? ? ? ? <property name="comparison_code" value="02"/>
? ? ? ? <property name="comparison_value" value=">"/>
? ? ? ? <property name="baseline" value="0"/>
? ? ? ? <property name="baseline_desc" value="客戶的身份證號碼在黑名單表個數(shù)中大于0"/>
? ? </rule>
</rules>
3膀藐, Drools文件方式屠阻,RuleEngine同樣支持讀取Drools的drl文件中的規(guī)則,并且可以直接執(zhí)行其規(guī)則體消请。此時需要在ruleEngine.properties中配置引擎的讀寫方式栏笆。僅僅配置兩項內(nèi)容即可类腮。
rule.reader=drools
drools.rule.filename=sample.drl
Drools的文件樣式請參考具體Drools的文檔臊泰,典型的樣式為:
#this is a test
package tech.kiwa.engine.entity;
globals java.util.List myGlobalList
import tech.kiwa.engine.sample.Student;
function void callOver(Student $student){
? if($student != null){
? System.out.println("student [" +? $student.name + "] is called.");
? }
}
function void ageUp(Student $student, int age ){
? if($student != null){
? $student.setAge( $student.getAge() + age);
? }
}
declare teacher
? age : int
? name : String
? sex : int
end
query "juniorBoy"
? $student: Student( age <=14 && (age >10 || age !=12 , sex? ==1 || sex == 2 ), name =="tony")
end
query "querymale"(int $gender)
? $student: Student(sex == $gender)
end
rule "ageUp12"
salience 400
when
? $student: Student(age < 8)
? /* antoher rule */
then
? System.out.println("I was called, my name is : " + $student.name);
? ageUp($student,12);
? //callOver($student);
end
rule "isTom"
salience 30
date-expires "2018-12-01"
dialet "java"
when
? $student: Student(name == tom)
then
? $student.sex = 4;
? ? callOver($student);
end
啟動RuleEngine,RuleEngine是針對一個具體的目標對象(Bean)進行檢測的蚜枢,該目標對象(Bean)必須是可讀的缸逃,必須提供對應(yīng)的get方法,或者其成員變量是public的厂抽。比如學(xué)生對象需频,我們需要檢測該學(xué)生對象是否符合我們定義的規(guī)則。因此筷凤,在調(diào)用規(guī)則引擎前昭殉,必須存在該對象,如果有多個對象藐守,那請在循環(huán)體中執(zhí)行該條件挪丢。
示例代碼如下:
? ? ? ? EngineService service = new EngineService();
? ? ? ? try {
? ? ? ? ? ? Student st = new Student();? //建立學(xué)生對象
? ? ? ? ? ? st.setAge(5);
? ? ? ? ? ? st.name = "tom";
? ? ? ? ? ? st.sex = 1;
? ? ? ? ? ? EngineRunResult result = service.start(st);
? ? ? ? ? ? System.out.println(result.getResult().getName());
? ? ? ? ? ? System.out.println(st.getAge());
? ? ? ? } catch (RuleEngineException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
上述代碼中,Sutdent對象存在getAge方法卢厂,因此是可以被訪問的乾蓬,其規(guī)則也是可以執(zhí)行的,否則會拋出RuleEngineException異常慎恒,在對應(yīng)的規(guī)則中任内,執(zhí)行了ageUp()的Command操作。