有天产品找我,“保健,帮我做个需求呗,根据用户的多维度条件来制定 ES 打分策略, 咱们来做 AB test”
我。。。。。心中一万个草泥马飘过。 这个产品是出了名的多变,需求变得相当快,我相信每个公司都有个产品大爷,需求永远跟夏天的天气一样多变,一会想要水里有的,一会想要天上飞的。
对于“用户多维度条件判断”,这一句话埋的坑就非常大,用户维度有很多
如果我全部写完,我估计三天三夜都写不完,总之一句话,这个维度非常广。如果我想着用面条式代码 if else 去写,本期需求也能完成。但是以后呢,这个就是个超级无敌大的坑,我得为自己接下的活负责。
在咨询组长后, 我总算是找到了一个解决方案:drools 流程引擎。这个技术我也是第一次见,听组长说能代替复杂的 if else ,速度很快, 而且在我们之前合规检测中使用过(快速检验素材是否合规)。当时组长还让我学学,以后说不定有用。
这个家伙号称只有产品想不到的需求,没有它实现不了的需求。就这么神奇。
drools 是一个易于调整和管理的开源业务规则引擎。不是业务流程。
速度快,兼容 java
公司开发充值发放优惠券活动,具体规则如下:
100 元,送 10 元优惠券
200 元,送 25 元优惠券
300 元,送 40 元优惠券
Java 后端攻城师在代码利用 if-else 代码将业务逻辑实现了功能,这样看似完全没有必要引入什么鬼规则引擎;
但问题出现了:几天后业务人员发现充值的人还是很少,就想修改发放优惠券活动:100 元送 15 元优惠券等......
这时候攻城师忍气吞声修改后端代码,并经过一大堆发布流程进行上线; 一段时间过后客户量多了,业务人员评估后有要减少优惠券的发放金额.......这时候,一场硝烟滚滚而来
所以适用场景为
<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>
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;
}
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
读取 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();
}
}
@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)
这里规则我放入到数据库中,便于以后上线修改。这里梳理了如何获得 KieSession
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();
}
这里我暂时没有特别好的办法,只能通过缓存数据来刷新,从而获得 kieSession 。
这里获取到的数据如果不在缓存,那么就查询数据库。这样子就实现了数据刷新。
List<SearchFactorFormulaRuleEntity> searchFactorFormulaRuleEntityList = factorFormulaRuleRepository.getFormulaList();
产品的需求是:根据用户的多维度条件来制定 ES 打分策略。下面是解决方案
这里我声明一个对象,里面直接丢个 map ,想要什么维度我就塞什么维度数据
public class SearchFormulaReq {
private String systemId;
/**
* 用于存储不定参数, 用 map 是为了方便升级
*/
private Map<String,Object> parameters;
}
这里我用公式 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
本以为这个需求被我征服了,能够满足产品这个“小小的需求”。至此,本期需求可以帮助产品完成他的需求:根据用户多维度进行条件判断。
但是当我写完后,找到产品,他却来了句“先按照单一需求来。。。不用那么花里胡哨”。我真是一万匹草泥马飘过。。。。。。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.