在许多应用程序中,业务决策依赖于一组规则,这些规则评估数据以产生结果或得出结论。规则引擎支持动态定义和执行业务规则,同时将其与应用程序代码解耦,从而更轻松地维护、扩展和管理应用程序中复杂的决策逻辑。
规则引擎在执行以结构化格式定义的业务规则时,提供了关注点分离、灵活性和可重用性。虽然市面上有Drools或Easy Rules等成熟的库,以及其他可以处理复杂规则管理的库,但在某些情况下,更简单的方法就足够了。这使我们能够在满足特定业务需求的同时,将依赖关系保持在最低限度。
在本教程中,我们将使用两种方法构建一个简单的规则引擎。首先,使用 Spring 表达式语言 (SpEL) 来实现动态规则;然后,使用基于 POJO 的方法,这种方法可以提供更高的类型安全性。
设置
要使用 Spring 表达式语言,我们需要spring-expression依赖项:
org.springframework
spring-expression
7.0.0-M7
我们现在将定义与规则引擎实现一起使用的模型类。
首先,让我们定义Customer类:
public class Customer {
private String name;
private int loyaltyPoints;
private boolean firstOrder;
// standard getters and setters
}
接下来,我们将定义Order类:
public class Order {
private double amount;
private Customer customer;
// standard getters and setters
}
使用SpEL(Spring表达式语言)
Spring 表达式语言 (SpEL)是一种支持在运行时查询和更新对象图的表达式语言。
它支持多种操作符,易于使用,并且与 Spring 集成良好。
定义规则
我们将使用 SpEL 定义规则,它可以在配置文件中创建,也可以直接在代码本身中创建,它采用表达式及其描述:
public class SpelRule {
private final String expression;
private final String description;
public SpelRule(String expression, String description) {
this.expression = expression;
this.description = description;
}
public boolean evaluate(Order order) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(order);
context.setVariable("order", order);
return parser.parseExpression(expression)
.getValue(context, Boolean.class);
}
// standard getters and setters
}
评估方法接受一个Order 类型的参数并将其设置到上下文中。然后,系统根据定义的表达式评估上下文,以确定输入值是否满足规则。
测试
我们将使用 SpEL 引擎尝试以下两个简单规则:
忠诚度折扣:如果忠诚度积分大于 500,则客户有资格享受折扣
首单高额折扣:如果是首次购买且订单金额大于 500,则提供特别折扣
让我们定义并验证以下这些规则:
@Test
void whenLoyalCustomer_thenEligibleForDiscount() {
Customer customer = new Customer("Bob", 730, false);
Order order = new Order(200.0, customer);
SpelRule rule = new SpelRule(
"#order.customer.loyaltyPoints > 500",
"Loyalty discount rule"
);
assertTrue(rule.evaluate(order));
}
@Test
void whenFirstOrderHighAmount_thenEligibleForSpecialDiscount() {
Customer customer = new Customer("Bob", 0, true);
Order order = new Order(800.0, customer);
SpelRule approvalRule = new SpelRule(
"#order.customer.firstOrder and #order.amount > 500",
"First-time customer with high order gets special discount"
);
assertTrue(approvalRule.evaluate(order));
}
我们可以看到,已经添加了两个新的业务规则,并在提供的输入数据上进行了验证。
基于POJO的规则引擎
现在,让我们探索一个基于 Java 的规则引擎,与 SpEL 方法相比,它提供了更好的类型安全性。
定义规则
我们首先定义一个Rule接口,它将为所有业务规则提供契约。该契约类似于 SpEL 引擎的契约;引擎根据传递给规则的Order来评估表达式。evaluate方法提供了更高的类型安全性,因为它使用输入对象的属性来定义和执行规则:
public interface IRule {
boolean evaluate(Order order);
String description();
}
接下来,我们将根据业务需求定义规则。
首先,我们从“忠诚度折扣”规则开始:
public class LoyaltyDiscountRule implements IRule{
@Override
public boolean evaluate(Order order) {
return order.getCustomer().getLoyaltyPoints() > 500;
}
@Override
public String description() {
return "Loyalty Discount Rule: Customer has more than 500 points";
}
}
接下来我们定义“首单高额折扣”规则:
public class FirstOrderHighValueSpecialDiscountRule implements IRule {
@Override
public boolean evaluate(Order order) {
return order.getCustomer()
.isFirstOrder() && order.getAmount() > 500;
}
@Override
public String description() {
return "First Order Special Discount Rule: First Time customer with high value order";
}
}
现在我们已经有了一些规则,让我们定义规则引擎,它将处理输入数据的这些规则:
public class RuleEngine {
private final List rules;
public RuleEngine(List rules) {
this.rules = rules;
}
public List evaluate(Order order) {
return rules.stream()
.filter(rule -> rule.evaluate(order))
.map(IRule::description)
.collect(Collectors.toList());
}
}
规则引擎有自己的评估方法,该方法接受输入参数。首先,它会评估所有规则,并最终返回满足条件的规则。
测试
我们已经根据业务需求定义了规则,并部署了规则引擎来处理这些规则。让我们运行一些测试来验证一下:
@Test
void whenTwoRulesTriggered_thenBothDescriptionsReturned() {
Customer customer = new Customer("Max", 550, true);
Order order = new Order(600.0, customer);
RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule()));
List results = engine.evaluate(order);
assertEquals(2, results.size());
assertTrue(results.contains("Loyalty Discount Rule: Customer has more than 500 points"));
assertTrue(results.contains("First Order Special Discount Rule: First Time customer with high value order"));
}
@Test
void whenNoRulesTriggered_thenEmptyListReturned() {
Customer customer = new Customer("Max", 50, false);
Order order = new Order(200.0, customer);
RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule()));
List results = engine.evaluate(order);
assertTrue(results.isEmpty());
}
我们在这里可以观察到,在第一个测试用例中,基于Customer和Order 输入,规则得到满足。另一方面,由于输入参数不满足业务需求,因此在第二个测试用例中,所有规则均不满足。
规则引擎方法比较
我们探讨了如何在两种实现中评估规则,并总结了每种方法的主要特点和权衡:
方面 SpEL 方法 POJO 方法
编译时安全 没有编译时安全性——错误只能在运行时捕获 具有编译时安全性
重构 底层属性发生变化时重构成本高 重构友好代码
调试 维护和调试复杂规则具有挑战性 易于调试
灵活性 灵活适应频繁的规则变化 规则频繁变化时灵活性较差
规则更新 规则更新无需更改代码 添加或更新规则需要更改代码并重新部署