Khi Marketing đổi ý lúc 5 giờ chiều: Grule Rule Engine và bài toán Promotion
Tech blog · Golang · Business Rule EngineThời gian đọc: ~10 phút

Hôm nay VIP giảm 20%.
Ngày mai thành 15%.
Tuần sau thêm Flash Sale. Tháng sau Birthday, Member Day, Brand Deal, Voucher stack… và một câu hỏi quen thuộc trong standup:
“Sửa con số thôi mà, sao phải deploy?”
Đó là lúc bạn nhận ra: business logic đang đặt nhầm chỗ.
Câu chuyện bắt đầu từ một đoạn if
Ban đầu một Promotion Engine rất “đơn giản”:
if user.IsVIP && order.Amount > 1_000_000 { discount = 20 } else if order.Amount > 500_000 { discount = 10 } if campaign == "SUMMER" { discount += 5 } if user.NewUser { discount += 10 } if payment == "VISA" { discount += 5 }
Sáu tháng sau, đoạn code đó không còn là đoạn code nữa. Nó trở thành một dĩa spaghetti.
Mỗi lần business đổi rule:
- Dev mở PR
- Reviewer hỏi “cái này stack với voucher được không?”
- QA regress cả checkout
- Build → Deploy
- …chỉ để đổi
20thành15
Công thức thất bại:
Business thay 1 con số → Engineering phải chạy cả pipeline.
Khi logic khuyến mãi bị gắn chặt vào application code, bạn không còn đang viết service nữa, chính xác hơn bạn đang viết CMS bằng Go.

Grule xuất hiện như một “nhà phán xử”
Grule không phải thư viện để “viết if đẹp hơn”. Nó là Business Rule Engine (BRE) dành cho Golang: bạn đưa facts vào, engine tự match, xếp hàng, và fire rule.
Thay vì hardcode trong Go:
rule VIPDiscount "VIP Discount" salience 100 { when User.IsVIP == true && Order.Amount > 1000000 then Discount.Value = 20; Retract("VIPDiscount"); }
rule NewUser { when User.NewUser == true then Discount.Value += 10; }
rule VisaPayment { when Payment.Method == "VISA" then Discount.Value += 5; }
Application chỉ còn việc:
engine.Execute(dataContext)
Bạn không gọi từng rule. Bạn không viết
else if dài 800 dòng. Bạn cung cấp dữ liệu. Grule quyết định luật nào thắng.Mô hình thường gặp trong Promotion Engine

Điểm quan trọng: service không cần biết business rule cụ thể. Nó chỉ cung cấp dữ liệu và nhận kết quả.
Vì sao Rule Engine hợp với hệ thống thay đổi liên tục?
1. Tách “luật chơi” khỏi “sân chơi”
Application lo:
- API, Database, Message Queue, Cache
- Logging, Monitoring, Quota, Lifecycle
Business rule sống riêng:
promotion.grl voucher.grl shipping.grl member.grl
Khi Marketing muốn thêm ShopeePay Promotion, bạn thêm rule mới — không cần mổ xẻ rule VIP cũ.

2. Thay đổi theo tốc độ business, không theo tốc độ release
Hardcode | Grule |
Đổi code → build → deploy | Sửa rule → reload |
Mỗi campaign = 1 PR | Mỗi campaign = 1 rule |
Conflict review kinh điển | Conflict resolution có sẵn ( salience) |
3. Hàng trăm rule vẫn quản lý được
Một Promotion Engine thực tế có thể có Flash Sale, Voucher, Brand, Category, Birthday, Payment, Member Level, Campaign, Supplier, Shipping…
Nếu tất cả nằm trong một file Go, maintain không còn là kỹ thuật — đó là sinh tồn.
Với salience, VIP (
100) luôn đi trước Voucher (50). Engine chọn rule theo ưu tiên, không theo “ai viết trước thì thắng”.4. Forward chaining: rule kích hoạt rule
Đây là phần hay nhất, cũng là phần dễ bị hiểu nhầm.

Bạn không gọi từng bước. Working Memory thay đổi → engine đánh giá lại → rule khác tự “bật”. Đó là lý do Grule được gọi là Inference Engine, không chỉ là DSL parser.
Bên trong chiếc hộp đen
Nếu chỉ thấy Grule như “file
.grl”, bạn bỏ lỡ phần thú vị nhất: nó dựa trên Rete Algorithm, cùng họ với Drools, nhưng dành riêng cho Go.Kiến trúc tổng quan

Năm thành phần chính
Thành phần | Vai trò ngắn gọn |
Rule DSL | Luật viết dạng when / then, không phải Go |
RuleBuilder | “Compiler” của rule: Lexer → Parser → AST |
KnowledgeBase | Kho luật — chỉ chứa logic, không chứa data |
DataContext | Cầu nối: User.IsVIP trong DSL → user.IsVIP trong Go |
Engine | Match → sắp Agenda → Execute |
Rule DSL trông như SQL thu nhỏ
rule VIPDiscount "VIP Discount" salience 100 { when User.IsVIP && Order.Total > 1000000 then Discount.Value += 20; Retract("VIPDiscount"); }
Cấu trúc gần như:
rule when then
DSL không phải Go code. Grule parse thành AST trước khi thực thi.
DataContext & Working Memory
dataCtx.Add("User", user) dataCtx.Add("Order", order) dataCtx.Add("Discount", discount)
Toàn bộ facts vào Working Memory. Rule chỉ đọc từ đây. không query DB, không gọi API. Nhờ vậy việc đánh giá rule diễn ra rất nhanh.

Deep dive: engine thực sự chạy như thế nào?
1. Rule Matching → Agenda
Engine kiểm tra fact nào khớp điều kiện rule. Những rule thỏa mãn được đưa vào Agenda, hàng đợi, chưa fire ngay.

Ví dụ Agenda sau khi match:
VIP salience 100 Birthday salience 80 Voucher salience 50 Visa salience 40
2. Salience & Conflict Resolution
Khi nhiều rule cùng đúng, engine chọn theo:
- Salience (ưu tiên cao hơn thắng)
- Rule order
- Insertion order
Nhờ vậy kết quả luôn nhất quán — miễn là bạn thiết kế priority có chủ đích.
3. Retract, phanh tay chống vòng lặp
Sau khi rule fire, nếu điều kiện vẫn đúng, rule có thể bị kích hoạt lại → vòng lặp vô hạn.
Retract("VIPDiscount")
Retract không phải “cú pháp cho có”. Thiếu Retract + salience loạn = kết quả “dead inside” lúc 2 giờ sáng.4. Rete Algorithm — trái tim của Grule
Giả sử có:
- 1.000 rules
- 1.000 facts
Cách ngây thơ: so sánh mọi thứ với mọi thứ ≈
1.000 × 1.000 lần.Rete xây mạng điều kiện. Fact
User đổi? Chỉ nhánh liên quan User được đánh giá lại — không đụng Order/Payment nếu không cần.
Node | Việc nó làm |
Alpha | Điều kiện trên 1 fact ( VIP == true, Age > 18) |
Beta | Nối nhiều fact ( User.ID == Order.UserID) |
Terminal | Đủ điều kiện → fire action |
5. Execution loop
Về bản chất:
for { matchedRules := Match() if len(matchedRules) == 0 { break } highest := Agenda.Pop() Execute(highest) UpdateWorkingMemory() }
Lặp đến khi không còn rule nào “muốn” chạy.

Bài học thực chiến
Theory đẹp. Production thì bẩn hơn một chút.
1. Business thường không muốn viết .grl
Họ muốn form trên Portal/CMS. Pattern hay gặp:

DSL vẫn là “ngôn ngữ máy”. UI mới là “ngôn ngữ người”.
2. Compile không free
Parse + build Rete có cost. Hot path (checkout) nên cache compiled KnowledgeBase, invalidate khi rule đổi, đừng compile lại mỗi request.
3. Priority trong DB = salience trong engine
Một con số, hai thế giới. Nếu mapping lệch, “VIP trước voucher” trên slide sẽ thành “voucher trước VIP” trên production.
4. Rule độc lập trên giấy, stack trên đời thật
Hai rule không sửa nhau trong source, nhưng cùng ghi vào
Discount thì vẫn conflict. Cần quy ước rõ:- Ai được cộng dồn?
- Ai exclusive?
- Ai phải
Retract?
5. Debug khó hơn if nhưng học được
Khi nhiều rule cùng đụng một fact, hãy log:
- Rule nào match?
- Salience bao nhiêu?
- Fact nào đổi sau khi fire?
Retractđã gọi chưa?
Không có observability cho Agenda = debug bằng trực giác.
Pros & Cons — nói thẳng
Ưu điểm
- Tách business logic khỏi source code — application sạch hơn, dễ bảo trì
- Mở rộng bằng cách thêm rule, không mổ
if-elsekhổng lồ
- Priority, conflict resolution, forward chaining có sẵn
- Hiệu năng tốt với số rule lớn nhờ Rete
- Rule độc lập hơn, giảm rủi ro “sửa A làm hỏng B”
Nhược điểm
- Learning curve cao — Working Memory, Agenda, Rete không phải thứ học trong một buổi
- Debug khó hơn đọc
iftuần tự
- Overkill nếu chỉ có vài điều kiện cố định
- Thiết kế rule kém (thiếu Retract, salience loạn) → loop hoặc kết quả ngoài mong đợi
- 500 rule không có convention = mê cung mới, chỉ đổi ngôn ngữ
Grule không xóa complexity. Nó dời complexity sang đúng chỗ có thể quản lý được, nếu bạn thực sự thiết kế tốt.
Kết: chọn công cụ, đừng chọn trend
Grule không phải phép màu để business tự đổi rule lúc 5 giờ chiều mà không cần ai. Nó cũng không phải thứ phải nhét vào mọi service Go.
Giá trị lớn nhất không nằm ở cú pháp:
rule ... when ... then ...
mà nằm ở việc bạn dám trả lời:
“Luật kinh doanh không thuộc về binary của lần deploy tuần trước.”
Hợp khi:
- Business đổi liên tục: Promotion, Pricing, Eligibility, Fraud, Policy, Approval Workflow
- Số rule lớn và tăng theo thời gian
- Muốn tách release engineering khỏi release campaign
Không hợp khi:
- Vài điều kiện cố định, hiếm khi đổi
- Team chưa sẵn sàng learning curve
- Chưa có quy ước đặt tên / nhóm rule / review rule
Với bài toán khuyến mãi, nơi hôm nay 20%, ngày mai 15%, tháng sau 50 campaign thì Rule Engine là cách để codebase còn thở được sau năm thứ hai.
Với một form validate 3 field?
if-else vẫn là người bạn trung thành nhất.Chọn đúng công cụ cho đúng bài toán luôn quan trọng hơn việc áp dụng một công nghệ chỉ vì nó nghe “enterprise”.
Tài liệu tham khảo
- So sánh tư duy với Drools / BRMS nếu bạn đến từ Java ecosystem
Nếu bạn đang cân nhắc đưa Rule Engine vào service Golang, câu hỏi hay nhất không phải “Grule mạnh không?” mà là: business rule của bạn đổi bao lâu một lần, và ai là người được phép đổi?

