用 Java 實作簡單的規則引擎
1.概述
在許多應用程式中,業務決策依賴一組規則,這些規則評估資料以產生結果或得出結論。規則引擎支援動態定義和執行業務規則,同時將其與應用程式程式碼解耦,從而更輕鬆地維護、擴展和管理應用程式中複雜的決策邏輯。
規則引擎在執行以結構化格式定義的業務規則時,提供了關注點分離、靈活性和可重複使用性。雖然市面上有 Drools 或 Easy Rules 等成熟的函式庫,以及其他可以處理複雜規則管理的函式庫,但在某些情況下,更簡單的方法就足夠了。這使我們能夠在滿足特定業務需求的同時,將依賴關係保持在最低限度。
在本教程中,我們將使用兩種方法來建立一個簡單的規則引擎。首先,使用 Spring 表達式語言 (SpEL) 來實現動態規則;然後,使用基於 POJO 的方法,這種方法可以提供更高的類型安全性。
2. 設定
要使用 Spring 表達式語言,我們需要spring-expression依賴項:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>7.0.0-M7</version>
</dependency>
我們現在將定義與規則引擎實作一起使用的模型類別。
首先,讓我們定義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
}
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. 定義規則
我們首先定義一個Rule
接口,它將為所有業務規則提供契約。該契約類似於 SpEL 引擎的契約;引擎根據傳遞給規則的Order
來evaluate
表達式。 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());
}
我們在這裡可以觀察到,在第一個測試案例中,基於Customer
和Order
輸入,規則得到滿足。另一方面,由於輸入參數不符合業務需求,因此在第二個測試案例中,所有規則均不符合。
5. 規則引擎方法比較
我們探討如何在兩種實作中評估規則,並總結了每種方法的主要特點和權衡:
方面 | SpEL 方法 | POJO 方法 |
---|---|---|
編譯時安全 | 沒有編譯時安全性-錯誤只能在執行時捕獲 | 具有編譯時安全性 |
重構 | 底層屬性改變時重構成本高 | 重構友善程式碼 |
偵錯 | 維護和調試複雜規則具有挑戰性 | 易於調試 |
靈活性 | 靈活適應頻繁的規則變化 | 規則頻繁變化時靈活性較差 |
規則更新 | 規則更新無需更改程式碼 | 新增或更新規則需要更改程式碼並重新部署 |
6. 結論
在本文中,我們探討如何從頭開始建立一個簡單的基於 Java 的規則引擎。
我們從基於 SpEL 的引擎開始,它在運行時評估動態規則,但維護和調試可能很困難,並且不提供編譯時檢查。
接下來,我們探索了基於 POJO 的引擎,它提供了更多的類型安全性和清晰度;然而,它引入了更嚴格的規則體系。
與往常一樣,程式碼可 在 GitHub 上取得。