EasyRules规则引擎介绍
简介
Easy Rules是一个Java规则引擎,灵感来自于Martin Fowler的一篇文章“我应该使用规则引擎吗?”
Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:
- 轻量级框架和易于学习的API
- 基于POJO的开发与注解的编程模型
- 定义抽象的业务规则并轻松应用它们
- 支持从简单规则创建组合规则的能力
- 支持使用表达式语言(如MVEL和SpEL)定义规则的能力
Easy Rules所提供了规则抽象,在规则中创建带有Condition
和Action
,以及提供了运行一组规则以评估条件和执行操作的RulesEngine
API。
API简介
eg : 定义一个规则
@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
}
eg :定义事件
Facts facts = new Facts();
facts.add("rain", true);
eg :规则驱动
public void test(Facts facts) {
Rules rules = new Rules();
rules.register(new HelloWorldRule());
// create a rules engine and fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
集成SpringBoot
EasyRule pom Configuration
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>4.0.0</version>
</dependency>
EasyRule yml Configuration
rules-engine:
skipOnFirstAppliedRule: false
skipOnFirstNonTriggeredRule: false
priorityThreshold: 1000000
rules:
- rulesId: testRuleId
rulesEtcdNodePath: /rule/config/testRuleId
contentType: json
testRuleId ETCD Node
[{
"name": "testRuleId",
"description": "testRuleId",
"priority": 1,
"compositeRuleType": "UnitRuleGroup",
"composingRules": [
{
"name": "AnyMatchCondition",
"description": "不存在,选择蔡徐坤",
"condition": "@testRrules.AnyMatchCondition(#rulesEngine)",
"priority": 1,
"actions": [
"@testRrules.AnyMatchAction(#rulesEngine)"
]
},
{
"name": "NoneMatchCondition",
"description": "不存在,选择黄佳星",
"condition": "@testRrules.NoneMatchCondition(#rulesEngine)",
"priority": 2,
"actions": [
"@testRrules.NoneMatchAction(#rulesEngine)"
]
}
]}
]
EasyRule Configuration Properties Class
@Data
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "rules-engine")
@Configuration
public class RulesEngineContextConfig {
private boolean skipOnFirstAppliedRule;
private boolean skipOnFirstNonTriggeredRule;
private boolean skipOnFirstFailedRule;
private int priorityThreshold;
private List rules;
}
@Data
public class RulesContent {
private String rulesId;
private String rulesEtcdNodePath;
private String contentType;
}
EasyRule Configuration Class
@Configuration
@Slf4j
public class EasyRuleConfig implements ApplicationContextAware {
private final String JSON = "json";
private final String YAML = "yaml";
@Autowired
EtcdClient etcdClient;
private ApplicationContext applicationContext;
@Autowired
private RulesEngineContextConfig contextConfig;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public BeanResolver createBeanResolver() {
return new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
}
/**
* 获取配置额规则列表
*
* @param beanResolver spring beanResolver
* @return Map
* @throws Exception
*/
@Bean
public RuleContext configRules(BeanResolver beanResolver) throws Exception {
RuleContext ruleContext = new RuleContext();
contextConfig.getRules().forEach(ruleContent -> {
Rules rules = null;
switch (ruleContent.getContentType()) {
case JSON:
SpELRuleFactory jsonRuleFactory = new SpELRuleFactory(new JsonRuleDefinitionReader(), beanResolver);
try {
rules = jsonRuleFactory.createRules(new StringReader(etcdClient.getString(ruleContent.getRrulesEtcdNodePath())));
} catch (Exception e) {
log.error("com.***.EasyRuleConfig.configRules,jsonRuleFactory,{}",e.getMessage(),e);
}
break;
case YAML:
SpELRuleFactory yamlRuleFactory = new SpELRuleFactory(new YamlRuleDefinitionReader(), beanResolver);
try {
rules = yamlRuleFactory.createRules(new StringReader(etcdClient.getString(ruleContent.getRrulesEtcdNodePath())));
} catch (Exception e) {
log.error("com.***.EasyRuleConfig.configRules,yamlRuleFactory,{}",e.getMessage(),e);
}
break;
default:
throw new IllegalStateException("Unexpected value: " + ruleContent.getContentType());
}
ruleContext.put(ruleContent.getRulesId(), rules);
});
return ruleContext;
}
@Bean
@ConditionalOnMissingBean(RulesEngine.class)
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RulesEngine rulesEngine() {
RulesEngineParameters parameters = new RulesEngineParameters();
if (contextConfig.getPriorityThreshold() > 0) {
parameters.setPriorityThreshold(contextConfig.getPriorityThreshold());
}
if (contextConfig.isSkipOnFirstAppliedRule()) {
parameters.setSkipOnFirstAppliedRule(contextConfig.isSkipOnFirstAppliedRule());
}
if (contextConfig.isSkipOnFirstNonTriggeredRule()) {
parameters.setSkipOnFirstFailedRule(contextConfig.isSkipOnFirstNonTriggeredRule());
}
if (contextConfig.isSkipOnFirstFailedRule()) {
parameters.setSkipOnFirstNonTriggeredRule(contextConfig.isSkipOnFirstFailedRule());
}
DefaultRulesEngine rulesEngine = new DefaultRulesEngine(parameters);
return rulesEngine;
}
}
EasyRule Driver Class
@Component
public class RuleDriver {
@Autowired
RulesEngine rulesEngine;
String key="delivery-rulesEngine";
public Facts driver( T t ,Rules rules){
Facts fact=new Facts();
fact.put(key,t);
rulesEngine.fire(rules,fact);
return fact;
}
public T fetchResult(Facts facts){
return facts.get(key);
}
}
Use EasyRules
// trigger rules
Facts fact=RuleDriver.driver(param,RuleContext.get("testRuleId"));
//fetchResult
T t=RuleDriver.fetchResult(fact);
总结
我个人理解,这个规则引擎偏向与策略模式的增强。简单轻量不假,但是代码侵入很高,耦合比较高,而且不支持web console
,还有就是当使用SPEL
或者是MVEL
表达方式来处理的情况下,同样是和业务代码绑定比较多,应用场景呢,我觉得是在业务发展相对平稳的情况下,如车辆性能评分,定价,数据来源相对固化的情况下使用。输入数据,得到规则触发之后的返回值。不过相对于Drools
来说,上手难易程度是很平滑,个人倾向注解大法好。目前我这一块使用主要是用来重构了确定性的一块规则。