javaweb收藏框架原理java学习

再见了 ! if-else !拥抱规则引擎

2018-05-04  本文已影响2151人  大方一号

分享是一种精神,是加深理解最好的方式之一

前言

现代编程日益复杂,面临如下问题
1、为提高效率,管理流程必须自动化,即使现代商业规则异常复杂。
2、市场要求业务规则经常变化,IT系统必须依据业务规则的变化快速、低成本的更新。
3、为了快速、低成本的更新,业务人员应能直接管理IT系统中的规则,不需要程序开发人员参与

插曲


世界上最遥远的距离,是我在if里你在else里

落地到具体实现只能盲目的、不停地 if-else,渐渐地,代码变得越来越庞大,继续维护起来 想吐!

回头反思一下, if-else不外乎以下若干场景

owner精神:如果if-else难以避免,如何正视它?

1、尽可能的合并各种条件分支

 if(条件A) {
  methodX()
 }
  中间隔着N多行代码
..........
 if(条件B) {
  methodX()
 }

通常以下场景会出现如上代码

重构后
if(条件A || 条件B ){
  methodX()
}

2、减少if-else嵌套

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();
}

3、彻底分离异常流程和主干流程

重构前

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参与过多的异常流程处理

4、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
}

5、if-else内的代码提取和封装成方法

伪代码:略

ps :设计模式大法替代if-else



规则引擎是什么

你可能仍然对为什么使用规则而感到困惑?如果只是一个或几个逻辑判断,确实没有必要使用规则引擎,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、 外部打码花钱如流水:全部切换到小费,然后继续观察
新的规则因子不断在增加.....
......................

规则引擎drools如何解决

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差不多已经可以解决我们复杂业务规则流程多变的系统,但是我们可以更进一步,把以上规则变更的锅 扔给业务人员。

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后直接写规则名的前缀,不用另写一列

.....

更为复杂的业务场景

三克油

富数技术沙龙还需要各位数据猛男们继续努力添砖加瓦!

猛男
上一篇 下一篇

猜你喜欢

热点阅读