Go 中的 Rule Cascade
模組:go get rulescascade.com/go(Go 1.26+,除標準函式庫和 gopkg.in/yaml.v3 外無其他依賴)。bundle 由 rcas compile --all 產生至 build/rules/。絕不要在執行時期解析 YAML,也絕不要將規則複製到程式碼中。
載入一次,每次請求評估
import rulecascade "rulescascade.com/go"
// 在啟動時執行。任何錯誤:不要啟動。
data, err := os.ReadFile("build/rules/<ruleset-id>.bundle.json")
doc, err := rulecascade.ParseJSON(data)
rules, err := rulecascade.FromBundle(doc)
建議使用中介軟體(自 1.0.0-alpha.7 起):rulescascade.com/go/rulehttp 從 JSON 請求主體(保持可讀性)建立請求、執行強制,並以 422 問題詳情回應被拒絕的請求,這在所有 Rule Cascade 執行時期都是相同的。
mux.Handle("POST /orders", rulehttp.Enforce(rules, rulehttp.Options{Entity: "Order", Operation: "create",
Actor: func(r *http.Request) map[string]any { return actorFromToken(r) }})(createOrder))
// 在處理程式中:rulehttp.FromContext(r.Context()) 是允許的結果
在其他地方,rules.Enforce(request, "server", nil) 回傳一個 *rulecascade.RuleViolation 錯誤(Problem() 是回應主體),而 rulehttp.WriteError(w, err) 則將其輸出。底層的 rules.Evaluate(request, "server", nil) 回傳一個 *rulecascade.Result(包含 Decision、Findings、Effects、Commands;result.Allowed())。*RuleSet 可安全地同時使用。每個有狀態變更的處理程式應遵循四個步驟:
- 在伺服器端評估,使用
map[string]any{"entity", "operation", "data", "original", "actor", "resolutions"}。 參與者來自驗證中介軟體,絕不來自呼叫端可以控制的標頭。使用rulecascade.ParseJSON(或json.Decoder.UseNumber())解碼請求主體,以確保數字保持精確。 - 拒絕時回應 422
application/problem+json(type: urn:rule-cascade:rule-violation)承載結果;對 gRPC 則使用codes.FailedPrecondition,並將 findings 放在詳細資訊中。 - 持久化,在套用
compute效果(Type == "value")之後:儲存伺服器的值。 - 在提交後執行
result.Commands,每個IdempotencyKey只執行一次(使用 outbox 表格或具有去重功能的 broker)。
放置方式:每個實體一個函式,用於建立請求並評估,從每個處理程式(net/http、chi、gin、echo 皆相同)呼叫。rulecascade.NewHolder 可以間隔或基於 cron 排程重新載入 bundle,並保留最後一次有效的規則。
取代現有的驗證
rcas analyze --module <module> 會列出 validate: 和 binding: 結構標籤、ozzo-validation、規則引擎、手動編寫的 errors.New 檢查、命名限制(const MaxQuantity = 10)、數字比較以及 switch 分支。帶有 drift 的候選項目表示專案的其他語言(通常是前端)以不同的值決定相同的名稱:必須由一個 ruleset 同時為兩者決定。在解碼階段保留形狀檢查;將商業決策移至規則。為舊檢查與引擎在相同輸入上添加表驅動的對等測試,然後刪除舊檢查。
測試
基於 Decision 和 finding 的 Code(而非訊息文字)進行表驅動測試。ruleset 中的黃金測試(rcas check)測試規則;CI 在 go test 之前執行 rcas check 和 rcas compile --all。