分享是一种精神,是加深理解最好的方式之一
现代编程日益复杂,面临如下问题
1、为提高效率,管理流程必须自动化,即使现代商业规则异常复杂。
2、市场要求业务规则经常变化,IT系统必须依据业务规则的变化快速、低成本的更新。
3、为了快速、低成本的更新,业务人员应能直接管理IT系统中的规则,不需要程序开发人员参与
我们势必都曾经经历过这样的场景
刚开始自己写的代码很简洁,逻辑清晰,函数精简,没有一个if-else
但是需求发展却不以人的意志为转移
业务逻辑千奇百怪
产品经理脑洞大开
项目进度越来约紧
落地到具体实现只能盲目的、不停地 if-else,渐渐地,代码变得越来越庞大,继续维护起来 想吐!
回头反思一下, if-else不外乎以下若干场景
if(条件A) {
methodX()
}
中间隔着N多行代码
..........
if(条件B) {
methodX()
}
通常以下场景会出现如上代码
重构后
if(条件A || 条件B ){
methodX()
}
if(条件A){
methodA();
.................
}else{
if (条件B){
methodB();
}else{
.................
if (条件C){
methodC();
}
}
}
代码嵌套了三层,会把自己和接锅的人绕晕!
其实嵌套if-else和外层业务逻辑并无关联性,完全可以提取到最外层,if-else互斥,尽量避免包含从属关系
if-else 最好是互斥关系!
重构后
if(条件A){
methodA();
}
if(条件B){
methodB();
}
if(条件C){
methodC();
}
重构前
if(result!=null){
code=result.get("code")
if(code.equals("200"){
data=result.get("data")
if(data.get("flow")!=null){
//处理流水信息
}else {
log.error("未抓取流水信息,tid:{},data:{}",tid,data)
}
}else{
log.error("获取数据失败,code:{} , msg :{} ",code,msg )
}else{
log.error("http请求失败")
}
}
重构后
if(result==null){
log.error("http请求失败");
return;
}
if(! code.equals("200"){
log.error("获取数据失败,code:{} , msg :{} ",code,msg )
return;
}
data=result.get("data")
if(data.get("flow")!=null){
log.error("未抓取流水信息,tid:{},data:{}",tid,data);
return
}
// TO DO 处理流水
tip 实际业务中逻辑远比上述demo复杂
同学们一定避免让if-else参与过多的异常流程处理
if(district=='南区' ){
//TO DO
//...........
if(companyid='ningbo' || companyid='hangzhou'){
//业务逻辑处理
}
}
*tip 实际处理的只是宁波和杭州两家分公司的业务,但是if的条件却是整个南区,if 条件分支处理尽可能的缩小范围
比如公积金社保json数据出现新case,尽量缩小case的范围,比如新case解析特殊方法加上条件判断
//常规流水解析
commonFlowParse()
//特殊case解析
if(orgid='gjj_ningbo' || orgid= 'gjj_zhengzhou'){
//处理特殊case
}
伪代码:略
你可能仍然对为什么使用规则而感到困惑?如果只是一个或几个逻辑判断,确实没有必要使用规则引擎,if-else 或者硬编码 可以更好地满足我们的需求。然而,业务规则往往是一个庞大且不断变化的规则组合,这使得系统非常复杂,如果只是使用常规代码,则会产生大量的维护工作
Drools 规则引擎基于 ReteOO 算法(对面向对象系统的Rete算法进行了增强和优化的实现),它将事实(Fact)与规则进行匹配,然后交给引擎去执行,将业务规则从应用程序代码中分离出来
规则因子如下
伪代码
- 结合配置文件
if( appid== kuaidai){
小费打码
}
if(orgid =='wuhan_gjj' || tianjin nanjing wenzhou .....){
云速打码
}ese if(orgid=='anhui_10086' || chengdu hefei ){
小费打码
}
.......
if(platform=101){
if( auto_retrys>2 || rate <50% ){
云速打码
}
if(type== 中文||.....) {
打码兔
}
.................
}else if(platform== 202){
打码兔
}
if( (orgid== suzhou_gjj || ningbo....) && all_retrys >1){
云速打码
}
if( all_retrys>3){
抛异常.....
}
1、目标网站验证码改版:比如杭州社保验证码是中文,刀哥完全不支持,需要紧急转移到小费打码,然后继续观察成功率,继续视情况而定再次迭代切换
2、机器学习训练集效果不错:温州,宁波社保小费打码校正完成了,成功率提升,外部打码可以切换过去
3、监控预警:grafana监控显示中国银行boc小费打码正确率只有10%,需要快速切换到云速打码
4、 临时需求:央行征信验证码小费或者偃月刀打码重试次数超过1次,转到外部打码
5、贝多多新商户接入验证码打码:API接口价格还未,定暂不使用外部打码,全部小费打码,然后观察监控
6、打码兔账户没钱了,紧急转移到云速打码
7、 外部打码花钱如流水:全部切换到小费,然后继续观察
新的规则因子不断在增加.....
......................
1、创建fact对象,设置规则因子
Router router=new Router();
//客户端调用入参,可以为空,下同
router.setAppid("贝多多appid");
router.setPlatform("101");
router.setSite("ningbo_gjj");
router.setTypeid("42");
//根据自动打码的tid重试次数计算得来
router.setAutoRetrys(3);
//根据打码的tid重试次数计算得来
router.setAutoRetrys(1);
//基于hashmap统计得来的
router.setRate(0.5F);
这是一种典型的OO思想,打码路由策略不再是复杂的if-else流程分支,而是去生成路由策略所需要的规则因子,构造Fact*JavaBean对象然后交给规则引擎去执行。
2、生成规则-drl数据文件
package router;
import com.xu.rules.dataobject.entity.Router;
rule "贝多多打码"
salience 100
date-expires "09-五月-2018"
no-loop true
when
$router : Router(appid.equals("beiduoduo"));
then
System.out.println("贝多多执行优先内部打码!");
$router.setResult("偃月刀打码.");
end
rule "云速打码"
salience 200
no-loop true
when
$router : Router( yunsuSite() contains site);
then
$router.setResult("云速打码.");
end
function String yunsuSite() {
String sites="nanjing_gjj,hangzhou_sb,tianjin_gjj,gz_10086@pc";
return sites;
}
3、规则文件预加载
drl文件需要先加载到drools工作内存,也就是加载到drools的容器中
KieServices kieServices = getKieServices();
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(() -> kieRepository.getDefaultReleaseId());
KieBuilder kieBuilder = kieServices.newKieBuilder(所有的规则文件);
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
// 验证drl规则文件的合法性
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
//构建规则文件
kieBuilder.buildAll();
//最终得到一个规则引擎容器
KieContainer kieContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
KieServices:drools的管理中心API,提供了CRUD,构建,管理和执行接口
kieRepository :管理规则的知识仓库
KieContainer:管理容器
.......
4、fact对象 碰撞 ”规则“
//构建规则因子
Router router = new Router();
router.setAppid(param.getAppid());
router.setPlatform(param.getPlatform());
router.setSite(param.getSite());
//获取session
KieSession kieSession = kieContainer.newKieSession();
//碰撞规则-插入进去
kieSession.insert(router);
// 执行 返回得到 命中的规则数量
int rules = kieSession.fireAllRules();
//资源释放
kieSession.dispose();
ps : 实际工作中,可以把规则引擎drl文件放在db中去维护,规则变更后,直接修改db,然后动态加载规则到drools的工作内存,系统无需重启,规则即时生效!规则引擎宿主机多实例的情况下,可以通过消息中间件消息订阅的形式,通知到所有的实例重载规则!
截止到上述介绍,规则引擎drools差不多已经可以解决我们复杂业务规则流程多变的系统,但是我们可以更进一步,把以上规则变更的锅 扔给业务人员。
通过应用规则引擎,将规则引擎中的决策表和Excel结合起来,将Excel数据文件直接导入到规则引擎的决策表中,然后决策表以规则的方式存储在规则库管理系统中。
Excel通过规则引擎中的规则包进行分门别类的方式保存,同时跟随规则包一起形成可追溯的规则版本,以便在需要的时候进行追溯查看
InputStream inputStream = new FileInputStream(excel);
//excel文件解析成drools的需要的格式
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
Resource resource = ResourceFactory.newInputStreamResource(inputStream, "UTF-8");
//最终得到规则文件drl的字符串
String rules = compiler.compile(resource, "rule-table");
//调取上述load方法,加载到工作内存
参数名 | 说明 |
---|---|
RuelSet | 在这个单元的右边单元中包含ruleset的名称 和drl文件中的package 是一样 |
CONDITION | 指明该列将被用于规则条件 CONDITION (代表条件) 相当于drl中的when |
ACTION | 指明该列将被用于推论,简单理解为结果 相当于drl中r then ACTION 与CONDITION 是平行的 |
PRIORITY | 指明该列的值将被设置为该规则行的'salience'值 |
RuleTable | 规则名,写法是 在RuleTable后直接写规则名的前缀,不用另写一列 |
.....
更为复杂的业务场景
作者:狗杞
链接:https://www.jianshu.com/p/9b67ab434795
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。