BRMS (Business Rule Management System) คือระบบที่ใช้สำหรับการจัดการ business logic ภายในองค์กร ซึ่งส่วนใหญ่แล้วก็จะมีความซับซ้อนสูง การนำ logic ไปฝังอยู่ใน software แต่ละ component ก็อาจจะดูไม่เหมาะสม การตรวจสอบและแก้ไขก็ทำได้ลำบาก การนำ BRMS มาใช้ ก็คือการรวบรวมนำเอา Business rule, requirement, policy ต่างๆ นำมาเขียนและจัดเก็บไว้ใน repository กลาง โดยผู้รวบรวมอาจจะเป็น user ผู้ใช้งานระบบเดิม หรืออาจจะเป็น programmer ก็ได้ โดย BRMS จะมี interface ที่ออกแบบมาให้ทั้ง user ทั่วๆไป (web interface, excel) และ programmer (DRL) มาเรียกใช้งานได้

Drools expert เป็นส่วนหนึ่งของ JBoss BRMS (Business Rule Management System) ซึ่งทำหน้าที่เป็น Rule Engine โดยหน้าที่ของ Rule Engine นั้นก็คือการนำเอาข้อมูล หรือ คำถาม (fact) มาเปรียบเทียบกับความรู้ (Knowledge) หรือ Rule นั่นเอง การกำหนด rule นั้นเราจะใช้ if clause ธรรมดา (ถ้า … แล้ว …) โดย Drools expert นี้ทำงานโดยใช้ Rete’s Algorithm คำว่า expert น่าจะมาจาก expert system ซึ่งก็คือระบบในการช่วยตัดสินใจนั่นเอง (เคยเรียนสมัยมหาลัย แต่ไม่รู้ว่ามันทำอย่างไร จนถึงทุกวันนี้😛 )

แล้วเมื่อไหร่ถึงควรใช้ Rule Engine ใน doc เค้าบอกไว้ว่า

– เมื่อปัญหามันซับซ้อนมากเสียจนกว่าเราจะเขียนออกมาเป็น code ธรรมดาได้

– เมื่อ logic ของปัญหานั้นเปลี่ยนแปลงบ่อย

– เมื่อคุณมี Business Analyst แต่ไม่มีความรู้ทางด้าน Technical

ถ้าสรุปง่ายๆจากมุมมองของโปรแกรมเมอร์ก็คือ อยากจะยก logic ออกไปจาก business layer ซะ ไม่อยากจะเขียน if else ซ้อนกันไปมาหลายๆชั้น โดยในทางปฏิบัติเราอาจจะแยกเป็น Rule Service ให้บริการระบบอื่นๆก็ได้

แล้วเมื่อไหร่ที่เราไม่ควรจะใช้ Rule Engine

Rule Engine ไม่ได้ออกแบบมาเพื่อทำงานแบบ Work Flow หรือการ Execute process ใดๆ การจำสร้าง Work Flow เราควรจะใช้ BPM มากกว่า หรือในบางครั้งการใช้ table ในการอ่านและอัพเดทก็เพียงพอแล้ว แต่ในบางครั้งการใช้ data table ในลักษณะนี้เพื่อเปลี่ยนลักษณะการทำงานของ โปรแกรมมันก็มากเกินไป จนทำให้ยุ่งยากและซับซ้อนเข้าไปอีก สุดท้ายแล้วไม่ว่าจะเป็นปัญหาแบบไหน การจะมาใช้ Rule Engine แล้วทำให้ชีวิตดีขึ้น ก็ถือว่าไม่ผิดแต่อย่างใด

ที่นี้เราลองมา implement Drools กันเล่นๆดีกว่า ในตอนนี้ผมใช้แค่ drools expert ในการสร้าง rule อย่างเดียวก่อน โดยจะทำผ่าน Jboss Developer Studio ที่ติดตั้ง plugin สำหรับ Drools โดยผมจะใช้ Drools version 6 ซึ่ง API ถูกแก้ไปบ้างนิดหน่อย ถ้าเทียบกับ version 5

รูปภาพเริ่มจากการสร้าง Drools project ก็ให้คลิกๆตามหน้าจอไป อย่าลืมกดสร้าง Drools runtime ด้วย หลังจากนั้นมันก็จะ generate project drools ออกมาให้เรา

โดยเราจะสร้าง Model กับ Rule ของเราขึ้นมาใหม่ โดยสถานการณ์ที่ผมคิดก็คือ เราจะทำการสร้าง rule สำหรับการ process order โดยมี

Class Order 

private Customer customer;
private int orderid;

//รายการ order
private List<Item> lineitems;

//Enum สำหรับการตรวจสอบ state (New,Processing,Processed)
private OrderState orderstate = OrderState.New;

Class Item แบบง่ายๆ

private String name;
private String manufacture;
private boolean processed = false;

Class customer แบบง่ายๆอีกเช่นกัน

private String name;
private String country;

หลังจากเราสร้าง Model ของเราเสร็จแล้ว เราก็จะลองไปสร้าง Rule กัน โดยให้สร้าง rule ใหม่ (Rule Resource) แบบธรรมดาไม่ต้องใช้ function (ใช้แล้ว error – ใข้ไม่เป็นนั่นเอง )

มาเริ่มกันที่ rule แรก แบบง่ายๆ

rule “For every new order”
when
order : Order(orderstate == OrderState.New)
then
System.out.println(order);
order.setOrderstate(OrderState.Processing);
end

rule ชื่อว่า “For every new order” จะทำงานเมื่อ มี Order เข้ามาและ orderState เป็น New หลังจากนั้นให้ print order ออกมาและแก้ orderState เป็น Processing โดย syntax ของมันก็จะทำนองว่า กำหนดตัวแปรชื่อ order จาก class Order โดยค่าของตัวแปร orderstate ใน order จะต้องเป็น New จะเป็นว่าใน ช่วงของ then เราสามารถเรียก Java code ได้เลย

Rule ถัดมายากขึ้น “For every line items that’s not been processed yet” สำหรับทุกๆ item ใน line item ที่ยังไม่ถูก process

rule “For every line items that’s not been processed yet”
when
order : Order(items : lineitems)
it : Item(processed == false) from items
then
InventoryUpdate.setTotalCount(InventoryUpdate.getTotalCount()+1);
it.setProcessed(true);
update(order)
end

ในส่วนของ When เราจะสร้างตัวแปรสองตัวคือ order และ items แล้วจะทำการเรียกแต่ละ item ที่ processed เป็น false จาก items และกำหนดตัวแปรของ item นั้นให้ชื่อว่า it (คล้ายกับ for-loop) จะเห็นว่าเราสามารถกำหนด “เงื่อนไข” ของเราได้หลายบรรทัด (ให้จินตนาการว่ามี and มาเชื่อมแต่ละบรรทัด) เงื่อนไขแรกคือ fact ที่เข้ามาจะต้องเป็น Class Order เงื่อไขที่สองคือ จะต้องมี item ที่ยังไม่ถูก process

เมื่อตรงตามเงื่อนไขทั้งสองแล้วก็จะเข้ามาที่ then โดยเราจะเรี่ยก static function setTotalCount ของ class InventoryUpdate ที่ไม่มีอะไรไปมากกว่าการแก้ตัวแปร totalCount โดยเราจะบวก totalCount เข้าไป 1 เพื่อบอกว่าเราขายของได้ 1 ชิ้นแล้วนะ หลังจากนั้นเราก็จะเซทว่า item นี้ถูก process ไปแล้วนะ และเรียกคำสั่ง update order

การ update order ก็คือการบอก rule engine ว่า fact / object ของเรานั้นมีการเปลี่ยนแปลง ให้ทำการ evaluate ใหม่อีกครั้ง

ส่วน rule สุดท้าย “When order was processed” เมื่อ Order ถูก process แล้ว

rule “When order was processed”
when
order : Order(orderstate == OrderState.Processing)
eval(LineItemHelper.itemAllTrue(order.getLineitems()))
then
System.out.println(“Order Completed total sale : ” + InventoryUpdate.getTotalCount());
order.setOrderstate(OrderState.Processed);

end

คล้ายกับ rule แรกแต่ที่นี้เราต้องการเช็คให้แน่ใจจริงๆว่า item ทุก item ถูก process ไปหมดแล้ว เนื่องจากผมหาวิธีการเช็คแบบนั้นไม่เจอ ว่าสามารถใช้ function อะไรช่วยได้มั้ย ผมเลยเขียน method ใน java ขึ้นมาชื่อ itemAllTrue เพื่อเช็คว่าทุกๆ item ถูก process เรียบร้อยแล้ว โดยตรงนี้เราจะต้องใช้ eval เข้ามาช่วย เพื่อทำการ execute java ซึ่งไม่จำเป็นต้องใช้ในส่วนของ then โดยการใช้ eval นี้ไม่ควรจะนำมาใส่ไว้เป็น rule แรกๆ เพราะ rule engine ไม่สามารถ compile แล้วมาเก็บไว้ในความจำมันได้ ดังนั้นจะต้องระวัง ไม่ควรใช้มากเกินไป และต้องวางให้ถูกที่ด้วย

ทีนี้เราจะลองมารัน Rule ทั้งหลายของเรานี้กัน

public static void main(String[] args) {

KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession(“ksession-rules”);

for (int i = 0; i < 100; i++) {
long start = System.currentTimeMillis();

Item item1 = new Item(“Galaxy S5”, “Samsung”);
Item item2 = new Item(“iPhone 5s”, “Apple”);
List<Item> lineitems = new ArrayList<>();
lineitems.add(item1);
lineitems.add(item2);
Customer customer1 = new Customer(“John Doe”, “Thailand”);

Order order1 = new Order(1, customer1, lineitems);

kSession.insert(order1);
kSession.fireAllRules();

System.out.println(System.currentTimeMillis() – start);
}
kSession.dispose();

โดยเราจะเรียก KieService (knowledge is everything) เพื่อไปเรียก container และทำการสร้าง session ใหม่จาก ksession-rules ซึ่งถูกกำหนดไว้ใน kmodule.xml ซึ่งมีข้อมูลของ package ที่เก็บไฟล์ rule ของเราทั้งหลายไว้อยู่ จากตัวอย่าง ถ้าเราทำการสร้าง session ใหม่ ksession-rules fact ของเราจะถูก validate เข้ากับทั้ง Sample.drl และ ProcessOrder.drl ด้วย

<kmodule xmlns=”http://jboss.org/kie/6.0.0/kmodule”&gt;
<kbase name=”rules” packages=”rules”>
<ksession name=”ksession-rules”/>
</kbase>

Output

Order ID: 1
Customer: John Doe
LineItems: [Galaxy S5, iPhone 5s]
Processed -> iPhone 5s
Processed -> Galaxy S5
Galaxy S5 from Korea
iPhone 5s from USA
Order Completed total sale : 200

ผลลัพธ์หลังจากรันไป 100 รอบ (ไม่ได้เปลี่ยน order id) แต่ละรอบใช้เวลาไม่เกิน 1 ms ยกเว้นรอบแรกใช้ไป 50ms ดังนั้นใครจะคิดใช้ Drools ควรจะทดลอง pilot นำร่องดูก่อน ว่าเหมาะสมกับสภาพแวดล้อมของเราหรือไม่ มีความสามารถในการเขียน rule ได้ดีแค่ไหน เขียนไม่ดีอาจจะติด loop เป็น recursive วิ่งวนไปเรื่อยๆก็เป็นไปได้