Everything about Grule Engine

Khi Marketing đổi ý lúc 5 giờ chiều: Grule Rule Engine và bài toán Promotion

Tech blog · Golang · Business Rule Engine
Thời gian đọc: ~10 phút

notion image
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:
  1. Dev mở PR
  1. Reviewer hỏi “cái này stack với voucher được không?”
  1. QA regress cả checkout
  1. Build → Deploy
  1. …chỉ để đổi 20 thành 15
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.
notion image

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

notion image
Đ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ũ.
notion image

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.
notion image
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

notion image

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.
notion image

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.
notion image
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:
  1. Salience (ưu tiên cao hơn thắng)
  1. Rule order
  1. 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.
notion image
Node
Việc nó làm
Alpha
Điều kiện trên 1 fact (VIP == trueAge > 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.
notion image

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:
notion image
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-else khổ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 if tuầ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?
 

Loading Comments...

Follow me @kevinbkdev

Donate to me
Bank QR

Bank QR Code

Buy me a coffee
Buy Me A Coffee

@source-blog by @thanhledev

Thứ 7 (24-12) lúc 9 giờ sáng mình có buổi workshop nhỏ chia sẻ cách viết Smart Contract dùng Solidity, target là chỉ cần biết code là làm được.
Nếu bạn hứng thú hãy tham gia nhé!