Spring Boot integrates easy rules engine easy-rules

Reasonable use of the rule engine can greatly reduce code complexity and improve code maintainability. The well-known open source rule engine in the industry is Drools, which is rich in functions, but also relatively large. In some simple scenarios, we only need a simple rule engine to meet the requirements.

This article introduces a small rule engine easy-rules , which is provided as a lib library, supports spring SPEL expressions, and can be well integrated in spring projects.

For specific code, refer to the sample project https://github.com/qihaiyan/springcamp/tree/master/spring-easy-rule

I. Overview

By configuring the business rules in the configuration file, the code can be streamlined, and it is easy to maintain. When the rules are modified, only the configuration file needs to be modified. easy-rules is a compact rule engine that supports spring's SPEL expressions, as well as Apache JEXL expressions and MVL expressions.

2. Add dependencies to the project

Add dependencies in the project's gradle.

build.gradle:

plugins {
    
    
    id 'org.springframework.boot' version '3.0.5'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'cn.springcamp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    
    
    compileOnly {
    
    
        extendsFrom annotationProcessor
    }
    testCompileOnly {
    
    
        extendsFrom testAnnotationProcessor
    }
}

repositories {
    
    
    mavenCentral()
}

dependencies {
    
    
    implementation "org.springframework.boot:spring-boot-starter-json"
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.jeasy:easy-rules-core:4.1.0'
    implementation 'org.jeasy:easy-rules-spel:4.1.0'
    implementation 'org.jeasy:easy-rules-support:4.1.0'
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation "org.springframework.boot:spring-boot-starter-test"
    testImplementation 'org.junit.vintage:junit-vintage-engine'
    testImplementation 'org.junit.vintage:junit-vintage-engine'
}

dependencyManagement {
    
    
    imports {
    
    
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:2022.0.1"
    }
}

test {
    
    
    useJUnitPlatform()
}

3. Configuration file

The sample program puts the business rules into the configuration file, the business rule configuration file (demo-rule.yml) code:

name: "age rule"
description: ""
priority: 1
condition: "#person.getAdult() == false"
actions:
  - "T(java.lang.System).out.println(\"Shop: Sorry, you are not allowed to buy alcohol\")"
  - "#person.setAdult(true)"
  - "#person.setAge(18)"
---
name: "alcohol rule"
description: ""
priority: 1
condition: "#person.getAdult() == true"
actions:
  - "T(java.lang.System).out.println(\"Shop: you are now allowed to buy alcohol\")"

The rules in the configuration file are configured through condition, and when the rules are satisfied, the actions configured in actions will be invoked.
The sample project uses spring's SPEL expression for rule configuration. Two rules are configured in the configuration file. The first rule judges whether the rule is satisfied through getAdult() in personthe spring bean, and calls three methods when the rule is satisfied.

In the configuration file of spring-boot itself, application.yml configures the rule file:

rule:
  skip-on-first-failed-rule: true
  skip-on-first-applied-rule: false
  skip-on-first-non-triggered-rule: true
  rules:
    - rule-id: "demo"
      rule-file-location: "classpath:demo-rule.yml"

4. Configure the rule engine in the code

Configure the rule engine through RuleEngineConfigthis spring configuration class:

@Slf4j
@EnableConfigurationProperties(RuleEngineConfigProperties.class)
@Configuration
public class RuleEngineConfig implements BeanFactoryAware {
    
    
    @Autowired(required = false)
    private List<RuleListener> ruleListeners;

    @Autowired(required = false)
    private List<RulesEngineListener> rulesEngineListeners;

    private BeanFactory beanFactory;

    @Bean
    public RulesEngineParameters rulesEngineParameters(RuleEngineConfigProperties properties) {
    
    
        RulesEngineParameters parameters = new RulesEngineParameters();
        parameters.setSkipOnFirstAppliedRule(properties.isSkipOnFirstAppliedRule());
        parameters.setSkipOnFirstFailedRule(properties.isSkipOnFirstFailedRule());
        parameters.setSkipOnFirstNonTriggeredRule(properties.isSkipOnFirstNonTriggeredRule());
        return parameters;
    }

    @Bean
    public RulesEngine rulesEngine(RulesEngineParameters rulesEngineParameters) {
    
    
        DefaultRulesEngine rulesEngine = new DefaultRulesEngine(rulesEngineParameters);
        if (!CollectionUtils.isEmpty(ruleListeners)) {
    
    
            rulesEngine.registerRuleListeners(ruleListeners);
        }
        if (!CollectionUtils.isEmpty(rulesEngineListeners)) {
    
    
            rulesEngine.registerRulesEngineListeners(rulesEngineListeners);
        }
        return rulesEngine;
    }

    @Bean
    public BeanResolver beanResolver() {
    
    
        return new BeanFactoryResolver(beanFactory);
    }

    @Bean
    public RuleEngineTemplate ruleEngineTemplate(RuleEngineConfigProperties properties, RulesEngine rulesEngine) {
    
    
        RuleEngineTemplate ruleEngineTemplate = new RuleEngineTemplate();
        ruleEngineTemplate.setBeanResolver(beanResolver());
        ruleEngineTemplate.setProperties(properties);
        ruleEngineTemplate.setRulesEngine(rulesEngine);
        return ruleEngineTemplate;
    }

    @Bean
    public RuleListener defaultRuleListener() {
    
    
        return new RuleListener() {
    
    
            @Override
            public boolean beforeEvaluate(Rule rule, Facts facts) {
    
    
                return true;
            }

            @Override
            public void afterEvaluate(Rule rule, Facts facts, boolean b) {
    
    
                log.info("-----------------afterEvaluate-----------------");
                log.info(rule.getName() + rule.getDescription() + facts.toString());
            }

            @Override
            public void beforeExecute(Rule rule, Facts facts) {
    
    
                log.info("-----------------beforeExecute-----------------");
                log.info(rule.getName() + rule.getDescription() + facts.toString());
            }

            @Override
            public void onSuccess(Rule rule, Facts facts) {
    
    
                log.info("-----------------onSuccess-----------------");
                log.info(rule.getName() + rule.getDescription() + facts.toString());
            }

            @Override
            public void onFailure(Rule rule, Facts facts, Exception e) {
    
    
                log.info("-----------------onFailure-----------------");
                log.info(rule.getName() + "----------" + rule.getDescription() + facts.toString() + e.toString());
            }
        };
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    
    
        this.beanFactory = beanFactory;
    }
}

ruleEngineTemplateThis spring bean is configured in the configuration file , and the execution of the rule engine is triggered through the ruleEngineTemplate.

5. Execution rule engine

ruleEngineTemplateAfter configuration, we can execute the rule engine in the business code to process the business rules configured in the configuration file:

As a demonstration, we put the execution code of the rule engine into the run method of Application, and execute the rule engine immediately after the program starts:

@SpringBootApplication
public class Application implements CommandLineRunner {
    
    

    @Autowired
    RuleEngineTemplate ruleEngineTemplate;

    public static void main(String[] args) {
    
    
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
    
    
        Person person = new Person();
        Facts facts = new Facts();
        facts.put("person", person);
        ruleEngineTemplate.fire("demo", facts);

    }
}

After the program is executed, you can see that it is printed in the console Shop: Sorry, you are not allowed to buy alcohol. This content corresponds to the configuration we configured in the actions in the rule file "T(java.lang.System).out.println(\"Shop: Sorry, you are not allowed to buy alcohol\")", indicating that the rule has been successfully executed.

Guess you like

Origin blog.csdn.net/haiyan_qi/article/details/129778299