この記事は、株式会社アトラエ アドベントカレンダー21日目の記事です。
BRMS
ビジネスルールをアプリケーションと切り離して管理するためのもので、業務要件やビジネスの制約をルールとして定義してアプリケーションで実行することができます。
BRMSを調査した背景
レガシーなサービスの大規模なリファクタを行っているのですが、その時に頭を悩ます問題なのが、 何に使われているデータなのか分からない ものと遭遇することです。
状態を表すカラムが存在し、さらにパッと見同じようなカラムが複数存在したり、抽象的な名前だったりで、コードを読みながら挙動を確認して理解するという調査の過程が作業時間のほとんどを占めています。
データの構造を理解した上でリファクタと同時にテストコードを書いていますが、進めるに連れて過去にリファクタした機能のビジネスルールが記憶から薄れていきました。
ビジネスルールをテストコードで確認するのはいささか辛いなと思い(ドキュメントにまとめてないのがいけないのですが。。。)、BRMSを用いてビジネスルールをまとめつつリファクタできないかと調査することにしました。
Gruleの使い方
Articleに is_open
と status
というデータがある場合の例、、、
Ruleを定義する
salience
は優先度を定義しています。複数ある場合値が大きい方が優先されます。
article.grl
rule CheckActive "Check Active" salience 100 {
when
A.IsOpen && A.Status == 1
then
A.IsActive = true
}
公開状態を表す値として isActive
を用意しています。
import (
"fmt"
"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"
)
func main() {
lib := ast.NewKnowledgeLibrary()
rb := builder.NewRuleBuilder(lib)
err := rb.BuildRuleFromResource("ArticleRule", "1.0", pkg.NewFileResource("path/to/article.grl"))
e := engine.NewGruleEngine()
knowledgeBase := lib.NewKnowledgeBaseInstance("ArticleRule", "1.0")
err = e.Execute(dataCtx, knowledgeBase)
if err != nil {
log.Printf("error: %v", err)
return
}
a := models.Article{
IsOpen: true,
Status: 0,
}
dataCtx := ast.NewDataContext()
err = dataCtx.Add("A", a)
if err != nil {
log.Printf("error: %v", err)
return
}
fmt.Printf("isActive: %v", a.IsActive)
// isActive: true
}
まだプロダクションに載せていませんが、仕様を理解しながらルールをまとめてドキュメントとしても活用できないか模索したいと思います。