メインコンテンツまでスキップ

RulesEngine 動態規則引擎指南

用途:以 JSON 定義業務規則,執行期動態評估,不需重新編譯部署

NuGetRulesEngine

授權:MIT

支援:.NET 6+、.NET Framework 4.6.2+

GitHubmicrosoft/RulesEngine


什麼是 RulesEngine

RulesEngine 是 Microsoft 開源的輕量級規則引擎。核心概念很簡單:把業務判斷邏輯從程式碼中抽出來,用 JSON 格式定義規則,在執行期動態載入和評估。

傳統做法是把判斷條件寫死在程式碼裡:

// ❌ 每次改條件都要改程式碼 → 重新編譯 → 重新部署
if (temperature > 80 && duration > 30)
{
await RaiseAlarmAsync(AlarmSeverity.Critical, "溫度過高");
}
else if (temperature > 60 && humidity > 90)
{
await RaiseAlarmAsync(AlarmSeverity.Warning, "高溫高濕");
}

RulesEngine 的做法是把這些條件放進 JSON:

[{
"WorkflowName": "AlarmRules",
"Rules": [
{
"RuleName": "HighTemperature",
"Expression": "Temperature > 80 AND Duration > 30",
"SuccessEvent": "Critical",
"ErrorMessage": "溫度 $(Temperature)°C 超過閾值"
},
{
"RuleName": "HighTempHighHumidity",
"Expression": "Temperature > 60 AND Humidity > 90",
"SuccessEvent": "Warning",
"ErrorMessage": "高溫高濕警告"
}
]
}]

規則存在資料庫或設定檔中,修改條件不需要改程式碼。現場工程師可以透過 UI 調整閾值,立即生效。


什麼場景該用 vs 不該用

✅ 適合使用 RulesEngine 的場景

場景原因
告警規則動態設定不同設備、不同客戶的告警閾值不同,現場需要自行調整
多租戶不同邏輯同一套系統,不同客戶 MES 的判斷規則不同
非開發人員需要修改規則現場工程師改告警條件,不應該需要工程師改程式碼
規則頻繁變更每週甚至每天都在調整的判斷條件
資料品質檢查感測器數據的合理性驗證,規則隨設備校正調整

❌ 不適合使用 RulesEngine 的場景

場景更好的選擇
固定不變的業務邏輯直接寫 C#,清楚且效能好
簡單的 if-else(< 5 條)程式碼更直覺,不需要引入框架
型別安全很重要RulesEngine 用字串表達式,沒有編譯期檢查
已有參數化的條件系統例如 EndConditionType enum + 參數的設計已經夠用
效能敏感的熱路徑表達式解析有開銷,每秒上萬次評估時考慮其他方案
判斷原則

問自己:「這個規則會不會需要在不部署程式碼的情況下修改?」 如果答案是「是」,就適合用 RulesEngine。如果規則穩定不變,直接寫 C# 更好。


核心概念

概念說明
Workflow一組相關規則的容器,每個 Workflow 有自己的名稱
Rule單一規則,包含名稱、表達式、成功/失敗事件
ExpressionC# Lambda 風格的表達式字串(由 System.Linq.Expressions 編譯)
RuleParameter傳入規則的資料物件(用 input1input2 在表達式中存取)
GlobalParamsWorkflow 層級的共用計算變數
LocalParamsRule 層級的區域計算變數
Actions規則通過/失敗時觸發的動作(內建 OutputExpression 或自訂 Action)

表達式語法

RulesEngine 的表達式基於 C# Lambda 語法,但用字串形式寫入 JSON:

// 比較運算
"Temperature > 80"
"Status == \"Running\""
"ErrorCount >= 3"

// 邏輯運算
"Temperature > 80 AND Pressure < 50"
"IsConnected == false OR ErrorCount > 10"

// 方法呼叫
"DeviceName.Contains(\"CMP\")"
"Tags.Any(t => t == \"Critical\")"

// 數學運算
"(Temperature - BaselineTemp) / BaselineTemp * 100 > 10"

評估流程

呼叫 ExecuteAllRulesAsync(workflowName, input) 後,RulesEngine 會依序走過以下步驟。每條 Rule 都會獨立評估一次,結果彙總為 RuleResultTree 清單:

流程重點:

  • Rules 會全部跑完(非短路):ExecuteAllRulesAsync 不會在第一條成功時停止;若只想取第一條通過的規則,需在呼叫端自行 .FirstOrDefault(r => r.IsSuccess)
  • OnSuccess / OnFailure 可選:不寫 Action 時,規則只回報 IsSuccess 結果;需要執行副作用(log、raise alarm)才寫 Action
  • LocalParams 與 GlobalParams:可在 Expression 中引用,前者作用範圍限於該 Rule,後者整個 Workflow 共用
  • 例外處理:Expression 編譯錯誤、型別不符等會落在 RuleResultTree.ExceptionMessage,不會中斷其他 Rule

規則結構

以 classDiagram 呈現 Workflow / Rule / Action 的組合關係:

JSON 與 C# 物件的對應:Workflow 對應最外層陣列元素,Rules 內的每個物件對應一個 Rule;執行結果會被包成 RuleResultTree(支援巢狀規則時的樹狀結果)。


安裝

dotnet add package RulesEngine

基本範例

using RulesEngine.Models;
using System.Text.Json;

// 1. 定義規則(通常從 DB 或檔案載入)
var workflowJson = @"[{
""WorkflowName"": ""TemperatureCheck"",
""Rules"": [
{
""RuleName"": ""CriticalTemperature"",
""Expression"": ""Temperature > 80"",
""SuccessEvent"": ""Critical""
},
{
""RuleName"": ""WarningTemperature"",
""Expression"": ""Temperature > 60 AND Temperature <= 80"",
""SuccessEvent"": ""Warning""
},
{
""RuleName"": ""NormalTemperature"",
""Expression"": ""Temperature <= 60"",
""SuccessEvent"": ""Normal""
}
]
}]";

var workflows = JsonSerializer.Deserialize<Workflow[]>(workflowJson)!;

// 2. 建立引擎
var rulesEngine = new RulesEngine.RulesEngine(workflows);

// 3. 準備輸入資料
var sensorData = new {
Temperature = 85.5,
Pressure = 101.3,
DeviceId = "CMP-01"
};

// 4. 執行規則
var results = await rulesEngine.ExecuteAllRulesAsync("TemperatureCheck", sensorData);

foreach (var result in results)
{
if (result.IsSuccess)
{
Console.WriteLine($"規則 {result.Rule.RuleName} 通過 → 事件: {result.Rule.SuccessEvent}");
// 輸出:規則 CriticalTemperature 通過 → 事件: Critical
}
}

程式碼量比較:RulesEngine vs 硬寫

當規則數量增長時,差異會越來越明顯:

// ❌ 硬寫:每加一條規則就要改程式碼,改完要部署
public AlarmSeverity EvaluateAlarm(SensorData data)
{
if (data.Temperature > 80 && data.Duration > 30) return AlarmSeverity.Critical;
if (data.Temperature > 60 && data.Humidity > 90) return AlarmSeverity.Warning;
if (data.Pressure < 50 || data.Pressure > 150) return AlarmSeverity.Warning;
if (data.FlowRate == 0 && data.PumpRunning) return AlarmSeverity.Critical;
// ... 20 條規則後,這個方法變成噩夢
return AlarmSeverity.Normal;
}

// ✅ RulesEngine:規則在 JSON/DB 中,程式碼永遠是同一段
public async Task<IEnumerable<AlarmSeverity>> EvaluateAlarmAsync(SensorData data)
{
var results = await _rulesEngine.ExecuteAllRulesAsync("AlarmRules", data);
return results
.Where(r => r.IsSuccess)
.Select(r => Enum.Parse<AlarmSeverity>(r.Rule.SuccessEvent));
}

延伸閱讀