Skip to content

性能基准测试

Ordo 规则引擎的全面性能基准测试,包括核心引擎微基准测试、HTTP 服务器吞吐量、分布式(NATS 同步)模式,以及与主流规则引擎的正面对比。

测试环境: Apple M1 Pro (10 核), 16 GB RAM, macOS Darwin 25.3.0 工具: Criterion.rs (微基准测试), hey (HTTP 压测), Docker (NATS) 日期: 2026-03-11


1. 核心摘要

指标数值
核心引擎执行 (4 分支规则)573 ns
HTTP 单实例峰值 QPS62,511
HTTP 分布式 (Writer + Reader 合计)83,082
批量执行峰值吞吐306 万条规则/秒
满载内存占用46.2 MB
vs Zen Engine (Rust 竞品) 核心速度快 7.3 倍
vs OPA (Go) HTTP 吞吐快 1.9 倍
vs json-rules-engine (Node.js) HTTP 吞吐快 3.4 倍

2. 核心引擎微基准测试

纯规则求值速度,使用 Criterion.rs 测量 — 无 HTTP、无中间件、无 I/O。每个引擎求值等价的 4 分支决策规则 (score >= 90 / 70 / 50 / default),输入 {"score": 75} 命中第二个分支。

2.1 Rust 引擎对比

引擎每次求值时间相对速度理论单核 QPS
原生 Rust if/else (天花板)3.5 ns285,000,000
Ordo (字节码 VM)573 ns1.0x1,746,000
Rhai (AST 解释器)728 ns0.79x1,374,000
Zen Engine / GoRules (图求值)4,200 ns0.14x238,000

核心结论: Ordo 的字节码 VM 比 Zen Engine 快 7.3 倍,比 Rhai 快 1.27 倍。

2.2 Ordo 详细微基准测试

表达式解析 (一次性开销)

表达式时间
age > 18 (简单比较)~1.0 µs
status == "active" (字符串相等)~1.0 µs
age > 18 && status == "active" (AND)~2.0 µs
age < 13 || age > 65 (OR)~2.2 µs
user.profile.level == "gold" (嵌套路径)~1.1 µs
status in ["active", "pending"] (集合成员)~1.9 µs

表达式求值 (每次执行, 已预编译)

表达式时间
age > 18~40 ns
status == "active"~54 ns
age > 18 && status == "active"~80 ns
user.profile.level == "gold"~63 ns
status in ["active", "pending"]~80 ns
score * 0.6 + bonus * 0.4 (算术)~67 ns

规则执行

场景时间
最小 2 步规则 (决策 → 终止)~361 ns
4 分支编译执行~573 ns
二进制编译 (.ordo 格式)~553 ns
批量 1K 执行吞吐~270 万 ops/sec

Schema JIT (Cranelift)

操作时间
JIT 字段访问 (原生代码)~5 ns
JIT 数值表达式~8 ns

3. HTTP 服务器基准测试

所有测试使用 hey,持续 10 秒。服务器以 release 模式运行,--log-level error 最小化 I/O 噪声。

3.1 单机模式 — 并发梯度测试

单 Ordo 实例,4 分支决策规则,输入 {"input":{"score":75}}

并发数QPS平均延迟P50P95P99MaxCPU%
114,6340.07 ms0.06 ms0.10 ms0.10 ms13.9 ms56%
1047,9600.21 ms0.19 ms0.30 ms0.40 ms10.6 ms423%
2557,2020.44 ms0.37 ms0.70 ms1.60 ms28.5 ms359%
5059,4400.84 ms0.76 ms1.60 ms2.90 ms51.9 ms411%
10061,0571.64 ms1.45 ms3.20 ms4.90 ms31.0 ms311%
20062,5113.18 ms2.78 ms7.00 ms9.90 ms39.2 ms309%
50060,5778.25 ms6.60 ms20.4 ms29.2 ms76.3 ms289%

饱和点: ~60K QPS(并发 50–200 稳定,500 时延迟明显上升)。

3.2 分布式模式 (Writer + Reader + NATS JetStream)

Writer 监听 :8080,Reader 监听 :8081,NATS 监听 :4222(Docker, 1 CPU, 256 MB)。

Writer (含 NATS 发布者)

并发数QPS平均P50P99
112,9040.08 ms0.07 ms0.20 ms
1044,8550.22 ms0.19 ms0.50 ms
5056,2550.89 ms0.80 ms3.20 ms
10058,5421.71 ms1.50 ms5.00 ms
20058,5963.41 ms2.90 ms10.9 ms

Reader (含 NATS 订阅者)

并发数QPS平均P50P99
114,1830.07 ms0.07 ms0.10 ms
1045,5880.22 ms0.19 ms0.50 ms
5058,5490.85 ms0.80 ms3.00 ms
10059,6801.68 ms1.50 ms5.10 ms
20060,2043.32 ms2.80 ms10.7 ms

NATS 同步对执行热路径零开销 — Writer/Reader QPS 与单机模式一致。

Writer + Reader 同时压测 (各 100 并发)

角色QPSCPURSS
Writer40,992193%30.9 MB
Reader42,090205%23.6 MB
合计83,08254.5 MB

在独立机器上部署时,每个 Reader 可线性增加 ~60K QPS。

3.3 批量执行吞吐

单机模式 50 并发,变化批量大小。

批量大小请求/秒规则执行/秒平均延迟
1053,018530,1800.9 ms
5033,8081,690,4001.5 ms
10022,0492,204,9002.3 ms
5006,1283,064,0008.2 ms

3.4 资源占用

指标空闲满载 (200 并发)
RSS 内存24.6 MB46.2 MB
线程数1111
CPU0%~550% (5.5 核)

4. 竞品对比

4.1 跨语言 HTTP 对比 (50 并发, 10 秒)

所有引擎求值等价的 4 分支逻辑,各自使用标准 HTTP 栈。

引擎语言QPS平均P50P99Max
Go net/http (硬编码)Go70,1340.71 ms0.60 ms2.30 ms16.0 ms
OrdoRust58,3740.86 ms0.80 ms3.40 ms19.8 ms
OPAGo30,3981.64 ms0.80 ms8.50 ms51.8 ms
json-rules-engineNode.js17,2052.91 ms2.60 ms6.10 ms110.5 ms
GruleGo6,5477.63 ms7.40 ms16.2 ms39.0 ms

Go net/http 硬编码是理论天花板(无规则引擎,纯 if/else + JSON 编解码)。Ordo 达到了其 83%

200 并发

引擎QPS平均P99
Go net/http (硬编码)69,2792.89 ms9.10 ms
Ordo60,3863.30 ms11.1 ms
OPA34,1665.85 ms27.8 ms
json-rules-engine16,29612.3 ms19.5 ms
Grule8,32824.0 ms50.6 ms

内存对比 (100 并发负载下)

引擎空闲负载CPU
Go net/http7.9 MB20.1 MB354%
Ordo8.3 MB25.7 MB545%
Grule11.8 MB30.5 MB558%
OPA26.1 MB50.7 MB442%
json-rules-engine32.6 MB122.1 MB112%

4.2 Rust 引擎 — 核心引擎速度 (无 HTTP)

使用 Criterion.rs 测量。每个引擎求值等价的 4 分支决策规则。

引擎每次求值时间对比 Ordo备注
Ordo573 ns1.0x字节码 VM + 预编译表达式
Rhai728 ns0.79xAST 树遍历解释器
Zen Engine (GoRules)4,200 ns0.14x图遍历 + 每次求值克隆

为什么 Ordo 核心更快

因素OrdoZen EngineRhai
求值方式寄存器式字节码 VM + 可选 Cranelift JIT解释式图遍历AST 树遍历解释
规则结构预编译步骤图,直接跳转每次 Arc<DecisionContent> 克隆 + 图遍历每次 Scope 分配 + String 克隆
每次求值分配接近零 (预编译)每次克隆决策图新 Scope + 变量拷贝
表达式编译一次编译为字节码每次解析 + 解释一次编译为 AST

4.3 Rust 引擎 — HTTP 对比 (Zen/Rhai 使用 actix-web)

为公平测试 HTTP 吞吐,Zen 和 Rhai 使用 actix-web(启用 keepalive,与 Ordo 的 Axum 一致)。

并发Ordo (Axum)Zen (actix-web)Rhai (actix-web)
113,61116,77618,307
5057,334105,165113,749
20061,615122,519128,828

HTTP 层面 Zen/Rhai 表现更高 QPS 的原因:

  1. 极简处理器 — 接收扁平 JSON,执行简单求值,返回最小 JSON。无中间件、无规则存储、无审计、无租户检查。
  2. actix-web thread-per-core 模型 — 对简单处理器的开销低于 Axum 的 work-stealing tokio 运行时。
  3. Ordo 每请求做的更多 — 规则存储查询 (DashMap)、中间件链 (租户、角色、审计采样、请求超时)、duration_us 计时、结构化响应含 output 字段。

CPU 效率 (100 并发):

引擎QPSCPUQPS/核
Rhai (actix-web)119K310%38,387
Zen (actix-web)121K348%34,770
Ordo (Axum)60K472%12,712

差距完全在 HTTP 服务层,而非引擎本身。如 §4.2 所示,Ordo 核心引擎比 Zen 快 7.3 倍,比 Rhai 快 1.27 倍。


5. 分布式同步功能测试结果

Writer 和 Reader 实例之间 NATS JetStream 同步的端到端验证。

测试项结果
Writer/Reader 启动 + NATS 连接
Writer 创建规则 → Reader 自动同步
Writer 更新规则 (v1→v2) → Reader 同步
Writer 创建第二条规则 → Reader 同步
Writer 删除规则 → Reader 同步删除
Reader 执行已同步规则 (所有分支)
Reader 批量执行
Reader 拒绝写操作 (409 Conflict)
服务器无限期运行(无 30 秒超时)

6. 基准测试期间修复的 Bug

6.1 执行器超时热路径回退

根因: default_timeout_ms0 改为 5000,导致 Instant::elapsed()(一个系统调用,macOS 上 ~20-30ns)在执行器热循环的每一步都被调用。

修复: 分摊超时检查 — 跳过前 16 步,之后每 16 步检查一次:

rust
// crates/ordo-core/src/rule/executor.rs
if timeout_ms > 0
    && depth >= 16
    && depth & 15 == 0
    && start_time.elapsed().as_millis() as u64 >= timeout_ms
{
    return Err(OrdoError::Timeout { timeout_ms });
}

同时实现条件步骤计时(仅在 tracing 启用时调用 Instant::now()):

rust
let (step_result, step_duration) = if tracing {
    let step_start = Instant::now();
    let result = self.execute_step(step, &mut ctx, &ruleset.config.field_missing)?;
    (result, step_start.elapsed().as_micros() as u64)
} else {
    let result = self.execute_step(step, &mut ctx, &ruleset.config.field_missing)?;
    (result, 0)
};

同样的分摊超时修复也应用于 compiled_executor.rs

影响: minimal_compiled 从 398ns 恢复到 361ns,批量吞吐从 250 万 → 270 万 ops/sec。

6.2 服务器 30 秒自动退出 Bug

根因: main.rstokio::time::timeout(shutdown_timeout, join_all(tasks)) 从服务器启动时就开始 30 秒倒计时,而非收到关闭信号后。服务器在启动 30 秒后无条件退出。

修复: 重构为 tokio::select! — 等待关闭信号或任务异常退出,仅在收到信号后启动超时倒计时:

rust
let all_tasks = futures::future::join_all(tasks);
tokio::pin!(all_tasks);

tokio::select! {
    _ = shutdown_signal => {
        // 收到信号 — 现在开始超时倒计时
        shutdown_tx.send(true).ok();
        match tokio::time::timeout(shutdown_timeout, &mut all_tasks).await {
            Ok(results) => { /* 优雅关闭 */ }
            Err(_) => { warn!("优雅关闭超时"); }
        }
    }
    results = &mut all_tasks => {
        // 服务器自行退出(崩溃或错误)
        for r in results { r??; }
    }
}

7. 复现这些基准测试

7.1 核心引擎微基准测试

bash
# 运行 Ordo 内置 Criterion 基准测试
cargo bench -p ordo-core

7.2 跨引擎核心基准测试

创建如下 Cargo.toml 的 Cargo 项目:

toml
[package]
name = "engine-bench"
version = "0.1.0"
edition = "2021"

[dependencies]
zen-engine = "0.25"
rhai = "1"
serde_json = "1"
criterion = { version = "0.5", features = ["html_reports"] }
tokio = { version = "1", features = ["rt"] }
ordo-core = { path = "path/to/crates/ordo-core" }

[[bench]]
name = "engines"
harness = false

基准测试文件 benches/engines.rs

rust
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ordo_core::prelude::*;
use serde_json::json;

fn ordo_setup() -> (RuleExecutor, RuleSet) {
    let json_str = r#"{
        "config": { "name": "bench", "entry_step": "s1" },
        "steps": {
            "s1": { "id":"s1","name":"S1","type":"decision","branches":[
                {"condition":"score >= 90","next_step":"high"},
                {"condition":"score >= 70","next_step":"mid"},
                {"condition":"score >= 50","next_step":"low"}
            ],"default_next":"fail"},
            "high": {"id":"high","name":"H","type":"terminal",
                     "result":{"code":"HIGH","message":"high tier"}},
            "mid":  {"id":"mid","name":"M","type":"terminal",
                     "result":{"code":"MID","message":"mid tier"}},
            "low":  {"id":"low","name":"L","type":"terminal",
                     "result":{"code":"LOW","message":"low tier"}},
            "fail": {"id":"fail","name":"F","type":"terminal",
                     "result":{"code":"FAIL","message":"failed"}}
        }
    }"#;
    (RuleExecutor::new(), RuleSet::from_json_compiled(json_str).unwrap())
}

fn zen_setup() -> (
    zen_engine::DecisionEngine<
        zen_engine::loader::NoopLoader,
        zen_engine::handler::custom_node_adapter::NoopCustomNode,
    >,
    std::sync::Arc<zen_engine::model::DecisionContent>,
) {
    let json_str = r#"{
        "nodes": [
            {"id":"input","type":"inputNode","name":"Input"},
            {"id":"table1","type":"decisionTableNode","name":"Check","content":{
                "inputs":[{"id":"s","name":"S","type":"expression","field":"score"}],
                "outputs":[
                    {"id":"c","name":"C","type":"expression","field":"code"},
                    {"id":"m","name":"M","type":"expression","field":"message"}
                ],
                "rules":[
                    {"_id":"r1","s":">= 90","c":"\"HIGH\"","m":"\"high tier\""},
                    {"_id":"r2","s":">= 70","c":"\"MID\"","m":"\"mid tier\""},
                    {"_id":"r3","s":">= 50","c":"\"LOW\"","m":"\"low tier\""},
                    {"_id":"r4","s":"< 50","c":"\"FAIL\"","m":"\"failed\""}
                ],
                "hitPolicy":"first"
            }},
            {"id":"output","type":"outputNode","name":"Output"}
        ],
        "edges":[
            {"id":"e1","sourceId":"input","targetId":"table1"},
            {"id":"e2","sourceId":"table1","targetId":"output"}
        ]
    }"#;
    let content: zen_engine::model::DecisionContent =
        serde_json::from_str(json_str).unwrap();
    (zen_engine::DecisionEngine::default(), std::sync::Arc::new(content))
}

fn rhai_setup() -> (rhai::Engine, rhai::AST) {
    let engine = rhai::Engine::new();
    let ast = engine.compile(r#"
        if score >= 90 { code = "HIGH"; message = "high tier"; }
        else if score >= 70 { code = "MID"; message = "mid tier"; }
        else if score >= 50 { code = "LOW"; message = "low tier"; }
        else { code = "FAIL"; message = "failed"; }
    "#).unwrap();
    (engine, ast)
}

fn bench_native(c: &mut Criterion) {
    c.bench_function("native_hardcoded", |b| {
        b.iter(|| {
            let score: f64 = black_box(75.0);
            let r = if score >= 90.0 { ("HIGH","high tier") }
                    else if score >= 70.0 { ("MID","mid tier") }
                    else if score >= 50.0 { ("LOW","low tier") }
                    else { ("FAIL","failed") };
            black_box(r)
        })
    });
}

fn bench_ordo(c: &mut Criterion) {
    let (executor, ruleset) = ordo_setup();
    let input: ordo_core::context::Value =
        serde_json::from_value(json!({"score": 75})).unwrap();
    c.bench_function("ordo_execute", |b| {
        b.iter(|| {
            black_box(
                executor.execute(black_box(&ruleset), black_box(input.clone())).unwrap()
            )
        })
    });
}

fn bench_zen(c: &mut Criterion) {
    let (engine, content) = zen_setup();
    let input = json!({"score": 75});
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all().build().unwrap();
    c.bench_function("zen_evaluate", |b| {
        b.iter(|| {
            let decision = engine.create_decision(content.clone().into());
            black_box(rt.block_on(decision.evaluate(black_box(&input))).unwrap())
        })
    });
}

fn bench_rhai(c: &mut Criterion) {
    let (engine, ast) = rhai_setup();
    c.bench_function("rhai_run_ast", |b| {
        b.iter(|| {
            let mut scope = rhai::Scope::new();
            scope.push("score", black_box(75.0_f64));
            scope.push("code", String::new());
            scope.push("message", String::new());
            let _ = engine.run_ast_with_scope(&mut scope, &ast);
            black_box(scope.get_value::<String>("code").unwrap())
        })
    });
}

criterion_group!(benches, bench_native, bench_ordo, bench_zen, bench_rhai);
criterion_main!(benches);

运行:

bash
cargo bench

7.3 HTTP 服务器基准测试

bash
# 构建带 NATS 同步支持的服务器
cargo build --release -p ordo-server --features nats-sync

# --- 单机模式 ---
./target/release/ordo-server \
  --role standalone --rules-dir /tmp/ordo-bench \
  -p 8080 --log-level error &

# 创建测试规则
curl -s -X POST http://localhost:8080/api/v1/rulesets \
  -H "Content-Type: application/json" \
  -d '{
    "config": {"name": "bench-rule", "entry_step": "s1", "version": "1.0.0"},
    "steps": {
      "s1": {"id":"s1","name":"S1","type":"decision","branches":[
        {"condition":"score >= 90","next_step":"high"},
        {"condition":"score >= 70","next_step":"mid"},
        {"condition":"score >= 50","next_step":"low"}
      ],"default_next":"fail"},
      "high": {"id":"high","name":"H","type":"terminal",
               "result":{"code":"HIGH","message":"high tier"}},
      "mid":  {"id":"mid","name":"M","type":"terminal",
               "result":{"code":"MID","message":"mid tier"}},
      "low":  {"id":"low","name":"L","type":"terminal",
               "result":{"code":"LOW","message":"low tier"}},
      "fail": {"id":"fail","name":"F","type":"terminal",
               "result":{"code":"FAIL","message":"failed"}}
    }
  }'

# 并发梯度测试
for C in 1 10 50 100 200 500; do
  echo "--- 并发: $C ---"
  hey -z 10s -c $C -m POST \
    -H "Content-Type: application/json" \
    -d '{"input":{"score":75}}' \
    http://localhost:8080/api/v1/execute/bench-rule
done

7.4 分布式模式测试

bash
# 启动 NATS
docker run -d --name nats -p 4222:4222 nats:latest -js

# Writer
./target/release/ordo-server \
  --role writer --rules-dir /tmp/ordo-writer \
  --nats-url nats://localhost:4222 --instance-id writer-1 \
  -p 8080 --grpc-port 50051 --log-level error &

# Reader
./target/release/ordo-server \
  --role reader --rules-dir /tmp/ordo-reader \
  --nats-url nats://localhost:4222 --instance-id reader-1 \
  --writer-addr http://localhost:8080 \
  -p 8081 --grpc-port 50052 --log-level error &

sleep 3

# 在 Writer 上创建规则
curl -s -X POST http://localhost:8080/api/v1/rulesets \
  -H "Content-Type: application/json" \
  -d '{"config":{"name":"bench-rule","entry_step":"s1","version":"1.0.0"},"steps":{"s1":{"id":"s1","name":"S1","type":"decision","branches":[{"condition":"score >= 90","next_step":"high"},{"condition":"score >= 70","next_step":"mid"},{"condition":"score >= 50","next_step":"low"}],"default_next":"fail"},"high":{"id":"high","name":"H","type":"terminal","result":{"code":"HIGH","message":"high tier"}},"mid":{"id":"mid","name":"M","type":"terminal","result":{"code":"MID","message":"mid tier"}},"low":{"id":"low","name":"L","type":"terminal","result":{"code":"LOW","message":"low tier"}},"fail":{"id":"fail","name":"F","type":"terminal","result":{"code":"FAIL","message":"failed"}}}}'

# 等待 NATS 同步
sleep 3

# 验证同步
curl -s http://localhost:8081/api/v1/rulesets  # 应该看到 bench-rule

# 在 Reader 上执行
curl -s -X POST http://localhost:8081/api/v1/execute/bench-rule \
  -H "Content-Type: application/json" \
  -d '{"input":{"score":75}}'

# 同时压测 Writer 和 Reader
hey -z 10s -c 100 -m POST \
  -H "Content-Type: application/json" \
  -d '{"input":{"score":75}}' \
  http://localhost:8080/api/v1/execute/bench-rule &

hey -z 10s -c 100 -m POST \
  -H "Content-Type: application/json" \
  -d '{"input":{"score":85}}' \
  http://localhost:8081/api/v1/execute/bench-rule &

wait

# 清理
docker stop nats && docker rm nats

7.5 跨语言 HTTP 对比

bash
# --- OPA ---
brew install opa
cat > /tmp/credit.rego << 'EOF'
package credit
default result := {"code": "FAIL", "message": "failed"}
result := {"code": "VIP", "message": "VIP tier"} if { input.score >= 90 }
result := {"code": "HIGH", "message": "high tier"} if { input.score >= 70; input.score < 90 }
result := {"code": "MID", "message": "mid tier"} if { input.score >= 50; input.score < 70 }
EOF
opa run --server --addr :9000 /tmp/credit.rego &
hey -z 10s -c 50 -m POST -H "Content-Type: application/json" \
  -d '{"input":{"score":75}}' http://localhost:9000/v1/data/credit/result

# --- json-rules-engine (Node.js) ---
# 完整服务器代码见 7.6 节
cd /tmp/json-rules-bench && npm install && node server.js &
hey -z 10s -c 50 -m POST -H "Content-Type: application/json" \
  -d '{"score":75}' http://localhost:9001/execute

# --- Grule (Go) ---
# 完整服务器代码见 7.7 节
cd /tmp/grule-bench && go build -o server . && ./server &
hey -z 10s -c 50 -m POST -H "Content-Type: application/json" \
  -d '{"score":75}' http://localhost:9003/execute

7.6 json-rules-engine 服务器 (Node.js)

javascript
// server.js
const { Engine } = require('json-rules-engine');
const fastify = require('fastify')({ logger: false });

function createEngine() {
  const engine = new Engine();
  engine.addRule({
    conditions: { all: [{ fact: 'score', operator: 'greaterThanInclusive', value: 90 }] },
    event: { type: 'VIP', params: { code: 'VIP', message: 'VIP tier' } },
    priority: 4,
  });
  engine.addRule({
    conditions: {
      all: [
        { fact: 'score', operator: 'greaterThanInclusive', value: 70 },
        { fact: 'score', operator: 'lessThan', value: 90 },
      ],
    },
    event: { type: 'HIGH', params: { code: 'HIGH', message: 'high tier' } },
    priority: 3,
  });
  engine.addRule({
    conditions: {
      all: [
        { fact: 'score', operator: 'greaterThanInclusive', value: 50 },
        { fact: 'score', operator: 'lessThan', value: 70 },
      ],
    },
    event: { type: 'MID', params: { code: 'MID', message: 'mid tier' } },
    priority: 2,
  });
  engine.addRule({
    conditions: { all: [{ fact: 'score', operator: 'lessThan', value: 50 }] },
    event: { type: 'FAIL', params: { code: 'FAIL', message: 'failed' } },
    priority: 1,
  });
  return engine;
}

const engine = createEngine();

fastify.post('/execute', async (request) => {
  const { score } = request.body;
  const { events } = await engine.run({ score });
  const top = events[0];
  return top ? top.params : { code: 'UNKNOWN', message: 'no match' };
});

fastify.get('/health', async () => ({ status: 'ok' }));
fastify.listen({ port: 9001, host: '0.0.0.0' });

7.7 Grule 服务器 (Go)

go
// main.go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "github.com/hyperjumptech/grule-rule-engine/ast"
    "github.com/hyperjumptech/grule-rule-engine/builder"
    "github.com/hyperjumptech/grule-rule-engine/engine"
    "github.com/hyperjumptech/grule-rule-engine/pkg"
)

const grl = `
rule VIP "VIP" salience 4 { when Input.Score >= 90 then Output.Code = "VIP"; Output.Message = "VIP tier"; Retract("VIP"); }
rule High "High" salience 3 { when Input.Score >= 70 && Input.Score < 90 then Output.Code = "HIGH"; Output.Message = "high tier"; Retract("High"); }
rule Mid "Mid" salience 2 { when Input.Score >= 50 && Input.Score < 70 then Output.Code = "MID"; Output.Message = "mid tier"; Retract("Mid"); }
rule Fail "Fail" salience 1 { when Input.Score < 50 then Output.Code = "FAIL"; Output.Message = "failed"; Retract("Fail"); }
`

type InputFact struct{ Score float64 }
type OutputFact struct{ Code, Message string }

var knowledgeLibrary *ast.KnowledgeLibrary

func init() {
    knowledgeLibrary = ast.NewKnowledgeLibrary()
    rb := builder.NewRuleBuilder(knowledgeLibrary)
    _ = rb.BuildRuleFromResource("Rules", "0.1.0", pkg.NewBytesResource([]byte(grl)))
}

func execute(w http.ResponseWriter, r *http.Request) {
    var req struct{ Score float64 `json:"score"` }
    json.NewDecoder(r.Body).Decode(&req)
    input := &InputFact{Score: req.Score}
    output := &OutputFact{}
    kb, _ := knowledgeLibrary.NewKnowledgeBaseInstance("Rules", "0.1.0")
    dataCtx := ast.NewDataContext()
    dataCtx.Add("Input", input)
    dataCtx.Add("Output", output)
    eng := &engine.GruleEngine{MaxCycle: 10}
    eng.Execute(dataCtx, kb)
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"code": output.Code, "message": output.Message})
}

func main() {
    http.HandleFunc("/execute", execute)
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"status":"ok"}`)) })
    fmt.Println("grule listening on :9003")
    http.ListenAndServe(":9003", nil)
}

基于 MIT 许可发布。