产品什么都要, 我差点给他真人修皮,反手就给他掏出个大宝贝(drools)

371 阅读4分钟

背景

有天产品找我,“保健,帮我做个需求呗,根据用户的多维度条件来制定ES打分策略, 咱们来做AB test”

我。。。。。心中一万个草泥马飘过。 这个产品是出了名的多变,需求变得相当快,我相信每个公司都有个产品大爷,需求永远跟夏天的天气一样多变,一会想要水里有的,一会想要天上飞的。

需求分析

对于“用户多维度条件判断”,这一句话埋的坑就非常大,用户维度有很多

  1. 用户Id
  2. 用户性别
  3. 用户角色
  4. 用户部门
  5. 等等等

如果我全部写完,我估计三天三夜都写不完,总之一句话,这个维度非常广。如果我想着用面条式代码 if else 去写,本期需求也能完成。但是以后呢,这个就是个超级无敌大的坑,我得为自己接下的活负责。

drools流程引擎

在咨询组长后, 我总算是找到了一个解决方案:drools流程引擎。这个技术我也是第一次见,听组长说能代替复杂的if else,速度很快, 而且在我们之前合规检测中使用过(快速检验素材是否合规)。当时组长还让我学学,以后说不定有用。

这个家伙号称只有产品想不到的需求,没有它实现不了的需求。就这么神奇。

简介

  1. 定义

drools是一个易于调整和管理的开源业务规则引擎。不是业务流程。

  1. 优点:

速度快,兼容java

  1. 使用场景

公司开发充值发放优惠券活动,具体规则如下:
100元,送10元优惠券
200元,送25元优惠券
300元,送40元优惠券
Java后端攻城师在代码利用if-else代码将业务逻辑实现了功能,这样看似完全没有必要引入什么鬼规则引擎;
但问题出现了:几天后业务人员发现充值的人还是很少,就想修改发放优惠券活动:100元送15元优惠券等......
这时候攻城师忍气吞声修改后端代码,并经过一大堆发布流程进行上线; 一段时间过后客户量多了,业务人员评估后有要减少优惠券的发放金额.......这时候,一场硝烟滚滚而来

所以适用场景为

  • 传统代码开发比较复杂繁琐
  • 没有优雅的算法
  • 经常变化的业务
  • 问题虽然不复杂,但是用传统的代码开发比较脆弱,需要经常修改
  • 有很多业务专家在(或者多变的产品经理),但是不懂技术

实践

  1. 基础实践

  1. pom配置



<dependency>

    <groupId>org.kie</groupId>

    <artifactId>kie-api</artifactId>

    <version>7.58.0.Final</version>

</dependency>

<dependency>

    <groupId>org.drools</groupId>

    <artifactId>drools-core</artifactId>

    <version>7.58.0.Final</version>

</dependency>

<dependency>

    <groupId>org.drools</groupId>

    <artifactId>drools-compiler</artifactId>

    <version>7.58.0.Final</version>

</dependency>

<dependency>

    <groupId>org.drools</groupId>

    <artifactId>drools-decisiontables</artifactId>

    <version>7.58.0.Final</version>

</dependency>

<dependency>

    <groupId>org.drools</groupId>

    <artifactId>drools-templates</artifactId>

    <version>7.58.0.Final</version>

</dependency>

  1. 先建立Person对象

package com.tezign.intelligence.search.entity;



import lombok.Data;



import java.math.BigDecimal;

import java.util.List;



@Data

public class Person {



    private int age;



    private int salary;



}
  1. 编写rule规则

drl文件建立在resources/rules 文件夹下

package rules;

dialect  "mvel"

import com.tezign.intelligence.search.entity.Person

import com.tezign.intelligence.search.entity.Age

import java.math.BigDecimal





rule "rule1"

    salience 3

    when

        $person:Person( age > 25 )

    then

        $person.salary = 3000;

    end
  1. 配置读取drl文件

读取resources/rules 文件夹下的drl文件

package com.tezign.intelligence.search.config;



import org.kie.api.KieBase;

import org.kie.api.KieServices;

import org.kie.api.builder.KieBuilder;

import org.kie.api.builder.KieFileSystem;

import org.kie.api.builder.KieModule;

import org.kie.api.builder.KieRepository;

import org.kie.api.builder.ReleaseId;

import org.kie.api.runtime.KieContainer;

import org.kie.api.runtime.KieSession;

import org.kie.internal.io.ResourceFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.core.io.support.ResourcePatternResolver;

import org.springframework.core.io.Resource;



import java.io.IOException;





@Configuration

public class KiaSessionConfig {



    private static final String RULES_PATH = "rules/";



    @Bean

    public KieFileSystem kieFileSystem() throws IOException {

        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();

        for (Resource file : getRuleFiles()) {

            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));

        }

        return kieFileSystem;

    }



    private Resource[] getRuleFiles() throws IOException {



        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

        final Resource[] resources = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");

        return resources;



    }



    @Bean

    public KieContainer kieContainer() throws IOException {



        final KieRepository kieRepository = getKieServices().getRepository();

        kieRepository.addKieModule(new KieModule() {

            public ReleaseId getReleaseId() {

                return kieRepository.getDefaultReleaseId();

            }

        });



        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());

        kieBuilder.buildAll();

        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());



    }



    private KieServices getKieServices() {

        return KieServices.Factory.get();

    }



    @Bean

    public KieBase kieBase() throws IOException {

        return kieContainer().getKieBase();

    }



    @Bean

    public KieSession kieSession() throws IOException {

        return kieContainer().newKieSession();

    }

}
  1. 进行规则测试



@Test

public void test(){

    Person person = new Person();

    person.setAge(26);

    session.insert(person);

    session.fireAllRules();

    log.info("formula {}",person.toString());

}



-----

输出结果

formula Person(age=26, salary=3000)
  1. 读取数据库的rule

这里规则我放入到数据库中,便于以后上线修改。这里梳理了如何获得KieSession

image.png



public KieSession getKieSession(){

    InternalKnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();

    List<SearchFactorFormulaRuleEntity> searchFactorFormulaRuleEntityList = factorFormulaRuleRepository.getFormulaList();

    for (int i = 0; ObjectUtils.isNotEmpty(searchFactorFormulaRuleEntityList) && i < searchFactorFormulaRuleEntityList.size(); i++) {

        KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

        SearchFactorFormulaRuleEntity formulaRule = searchFactorFormulaRuleEntityList.get(i);

        if(StringUtils.isNoneBlank(formulaRule.getRuleValue())){

            StringReader stringReader = new StringReader(formulaRule.getRuleValue());

            Resource resource = ResourceFactory.newReaderResource(stringReader);

            knowledgeBuilder.add(resource, ResourceType.DRL);

            knowledgeBase.addPackages(knowledgeBuilder.getKnowledgePackages());

        }

    }

    return knowledgeBase.newKieSession();

}
  1. 动态刷新rule

这里我暂时没有特别好的办法,只能通过缓存数据来刷新,从而获得kieSession。

这里获取到的数据如果不在缓存,那么就查询数据库。这样子就实现了数据刷新。

List<SearchFactorFormulaRuleEntity> searchFactorFormulaRuleEntityList = factorFormulaRuleRepository.getFormulaList();
  1. 如何维护后续rule

    1. 修改数据库
    2. 清楚redis中的缓存数据

适应产品的需求

产品的需求是:根据用户的多维度条件来制定ES打分策略。下面是解决方案

  1. 解决用户不同维度

这里我声明一个对象,里面直接丢个map,想要什么维度我就塞什么维度数据



public class SearchFormulaReq {



    private String systemId;



    /**

 * 用于存储不定参数, 用map是为了方便升级

 */

 private Map<String,Object> parameters;



}
  1. 解决不同维度的打分方案

这里我用公式id表示选择的打分公式,这块的逻辑没有用if else包装,而是用drools来做,以后上线也方便。直接上SQL脚本,往MAP里丢数据就行了

package rules;

dialect  "mvel"

import com.tezign.intelligence.tenant.manager.search.domain.SearchRuleDomain;

import java.util.ArrayList;





rule "rule1"

    when

        $ruleDomain:SearchRuleDomain( systemId == "t1"  && parameters["userId"] % 2 == 0 )

    then

        System.out.println("$ruleDomain = " + $ruleDomain);

        $ruleDomain.formulaIdList.add(2L);

    end

产品一席话

本以为这个需求被我征服了,能够满足产品这个“小小的需求”。至此,本期需求可以帮助产品完成他的需求:根据用户多维度进行条件判断。

但是当我写完后,找到产品,他却来了句“先按照单一需求来。。。不用那么花里胡哨”。我真是一万匹草泥马飘过。。。。。。

参考

www.jianshu.com/p/de5789b29…