1. 概述
在許多應用程序中,業務決策依賴於一組規則,這些規則對數據進行評估以產生結果或得出結論。 規則引擎允許動態定義和執行業務規則,同時將其與應用程序代碼解耦,從而使其更易於維護、擴展和管理應用程序中的複雜決策邏輯。
規則引擎提供分層設計、靈活性和可重用性,同時執行以結構化格式定義的業務規則。 儘管存在成熟的庫,例如 Drools 或 Easy Rules,以及其他可以處理複雜規則管理的庫,但在某些情況下,更簡單的方案也足夠。 這使我們能夠最大限度地減少依賴關係,同時滿足特定的業務需求。
在本教程中,我們將使用兩種方法構建一個簡單的規則引擎。 首先,使用 Spring Expression Language (SpEL) 進行動態規則,然後使用 POJO(Plain Old Java Object)方法,該方法提供更強的類型安全。
2. 安裝配置
要使用 Spring Expression Language,我們需要添加 spring-expression 依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>7.0.0-M7</version>
</dependency>現在我們將定義用於與規則引擎實現的模型類。
首先,讓我們定義 客户 類:
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
}3. 使用 SpEL (Spring 表達式語言)
Spring 表達式語言 (SpEL) 是一種支持在運行時查詢和更新對象圖的表達式語言。
它支持多種運算符,易於使用,並且與 Spring 集成良好。
3.1. 定義規則
我們將使用 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
}evaluate 方法接受一個類型為 Order 的參數,並將其置於上下文中。然後,系統將上下文中與定義的表達式進行比較,以確定輸入值是否滿足規則。
3.2. 測試
我們將使用以下兩個簡單規則來測試 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));
}我們能看到,兩個新的業務規則已添加到提供的輸入數據中並已驗證。
4. 基於 POJO 的規則引擎
現在,讓我們探索一個基於 Java 的規則引擎,它相比 SpEL 提供了更好的類型安全性。
4.1. 定義規則
我們將首先定義一個 <em >Rule</em > 接口,該接口將為所有業務規則提供合同。合同與我們為 SpEL 引擎提供的合同類似;引擎將評估表達式與傳遞給規則的 <em >Order</em >> 進行比較。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<IRule> rules;
public RuleEngine(List<IRule> rules) {
this.rules = rules;
}
public List<String> evaluate(Order order) {
return rules.stream()
.filter(rule -> rule.evaluate(order))
.map(IRule::description)
.collect(Collectors.toList());
}
}規則引擎擁有自己的 evaluate 方法,它接受輸入參數。 引擎首先評估所有規則,最後返回滿足這些規則的規則。
4.2. 測試
我們根據業務需求定義了規則,並部署了規則引擎來處理這些規則。下面我們運行一些測試以驗證它們:
@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<String> 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<String> results = engine.evaluate(order);
assertTrue(results.isEmpty());
}如我們所見,在第一個測試用例中,規則是基於 客户 和 訂單 輸入而滿足的。另一方面,由於輸入參數不符合業務要求,因此在第二個測試用例中,沒有規則得到滿足。
5. 規則引擎方法比較
我們研究了兩種實現中規則的評估方式,並總結了每種方法的關鍵特性和權衡:
| 方面 | SpEL 方法 | POJO 方法 |
|---|---|---|
| 編譯時安全性 | 無編譯時安全性 – 錯誤僅在運行時捕獲 | 具有編譯時安全性 |
| 重構 | 底層屬性發生變化時,重構成本高 | 易於重構的代碼 |
| 調試 | 維護和調試複雜規則具有挑戰性 | 易於調試 |
| 靈活性 | 適用於頻繁規則更改 | 規則頻繁更改時,靈活性較低 |
| 規則更新 | 無需代碼更改即可更新規則 | 添加或更新規則需要代碼更改和重新部署 |
6. 結論
在本文中,我們探討了如何從零開始構建一個簡單的基於 Java 的規則引擎。
我們首先從基於 SpEL 的引擎開始,該引擎在運行時評估動態規則,但維護和調試起來具有挑戰性,並且不提供編譯時檢查。
接下來,我們探索了基於 POJO 的引擎,該引擎提供更高的類型安全性和清晰度;然而,它引入了更嚴格的規則系統。