EasyRule规则引擎

Scroll Down

EasyRules规则引擎介绍

简介

Easy Rules是一个Java规则引擎,灵感来自于Martin Fowler的一篇文章“我应该使用规则引擎吗?

Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:

  • 轻量级框架和易于学习的API
  • 基于POJO的开发与注解的编程模型
  • 定义抽象的业务规则并轻松应用它们
  • 支持从简单规则创建组合规则的能力
  • 支持使用表达式语言(如MVEL和SpEL)定义规则的能力

Easy Rules所提供了规则抽象,在规则中创建带有ConditionAction,以及提供了运行一组规则以评估条件和执行操作的RulesEngineAPI。

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来说,上手难易程度是很平滑,个人倾向注解大法好。目前我这一块使用主要是用来重构了确定性的一块规则。