Drools Expert System Example
In this article, we will see an example of Drools Expert system. First, let’s try to understand what is an expert system?
An expert system’s goal is to help make a decision or solve a problem. Now to make a proper decision, it relies on are knowledge system and the working memory where we have the data that is to be applied on the knowledge system.
If you want to more know about Drools Introduction or its setup, read here.
This example uses the following frameworks:
In your pom.xml
, you need to add the below dependencies:
knowledge-api
– this provides the interfaces and factoriesdrools-core
– this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules.drools-complier
– this contains the compiler/builder components to take rule source, and build executable rule bases. You don’t need this during runtime, if your rules are pre-compiled.
1. Dependencies
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks.drools</groupId> <artifactId>droolsHelloWorld</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> </dependencies> <properties> <drools.version>6.2.0.Final</drools.version> <jbpm.version>6.2.0.Final</jbpm.version> </properties> </project>
2. Expert System
Expert Systems use Knowledge representation to facilitate the codification of knowledge into a knowledge base which can be used for reasoning, i.e., we can process data from the working memory with this knowledge base to infer conclusions. The knowledge system is composed of analytical rules defined by experts. The next diagram basically represents the structure of an expert system. Drools is a Rule Engine that uses the rule-based approach to implement an Expert System. The facts and data are applied against Production Rules to infer conclusions which result in actions. The process of matching the new or existing facts against Production Rules is called Pattern Matching, which is performed by the Inference Engine. The inference engine models in the lines of human reasoning process.
3. Rule Structure
A rule is composed of two main structures.
when <conditions> then <actions>;
For example,
rule "Add Processed CartItem to Order" when $cartItem : CartItem(cartStatus == CartStatus.PROCESSED) then System.out.println("\nAdd Processed CartItem to Order Rule"); end
4. Inference Engine
An inference engine follows the following steps to figure out the rules to apply:
- Inference engine depends on two set of memory, the production memory to access the rules and the working memory to access the facts.
- Facts are asserted into the working Memory where they may then be modified or retracted. We will see an example of this.
rule "Print Cart Issues" salience -1 when $cart : Cart() then if ($cart.hasIssues()) { System.out.println("\nPrint Cart Issues Rule"); System.out.println("************************************"); System.out.println($cart.getCartIssues()); insert($cart.getPendingItems()); } end
- A system with a large number of rules and facts may result in many rules being true for the same fact assertion; these rules are said to be in conflict. Inference uses a Conflict Resolution strategy to resolve the order in which rules need to be fired.
5. Forward Chaining
Drools implements and extends the Rete algorithm. It relies on forward chaining. What is a forward chaining?
Forward chaining is “data-driven”, with facts being asserted into working memory, which results in one or more rules being concurrently true and scheduled for execution by the Agenda. In short, we start with a fact, it propagates and we end in a conclusion.
Here is a forward chain flow:
- Cart Item is Processed.
- If the cart item is processed, create an order for it.
- Since cart item is already processed, result would be ‘Create an Order for the processed cart item’.
Backward chaining is “goal-driven”, meaning that we start with a conclusion which the engine tries to satisfy.
In case of backward chain, the above would look like:
- The order is to be created for a cart item.
- If a cart item is processed, create an order.
- Result in this case would be it picks up cart items that are already processed.
Drools plans to provide support for Backward Chaining in a future release.
Here is an example. Assume product qty is 0, inference engine will end up selecting both ‘Is Out-Of Stock’ rule and ‘Verify Qty’.
package com.javacodegeeks.drools; import com.javacodegeeks.drools.Cart; import com.javacodegeeks.drools.CartItem; import com.javacodegeeks.drools.CartStatus; import com.javacodegeeks.drools.Product; import java.util.List; global List outOfStockProducts; function String pendingItemKey(CartItem cartItem) { return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc(); } //Is it out of stock? rule "Is Out-Of Stock" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0) then System.out.println("\nIs Out-Of Stock Rule"); System.out.println("************************************"); String error = "Can't process as " + $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ; System.out.println(error); $cartItem.setErrors(true); $cartItem.setError(error); $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem); outOfStockProducts.add($cartItem.getProduct()); end rule "Verify Qty" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty()) then System.out.println("\nVerify Qty Rule"); System.out.println("************************************"); String error = "Can't process as only " + $cartItem.getProduct().getAvailableQty() + " of " + $cartItem.getProduct().getDesc() + " are left whereas qty requested is " + $cartItem.getQty(); System.out.println(error); $cartItem.setErrors(true); $cartItem.setError(error); $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem); end
6. Rule Engine
Here are some important points about rule engine.
- Rule engine is all about declarative programming. We only declare what is to be done.
- The how part is based on the data and the behavior which is decoupled from the rules.
- Rule engines allow you to say “What to do”, not “How to do it”.
- Since each problem is a rule, it improves readability.
- Your data is in your domain objects, the logic is in the rules. This is adavantage if there are many rules and one wants the flexibility of adding rules without changing the existing system.
- You can have rules in more than one file, this way its easy to manage rules in case you have many.
- Finally, we end up creating a repository of knowledge which is executable.
- The rules also serve as documentation as they are have better readability than code.
7. Drools Expert System Example
We will use an example of shopping cart which contains cart and cart items.
Customer will add one or more products to the cart. There are certain rules that we want to fire as we process the cart.
Rules are:
- If a product needs registration, customer need to register else the item will not be processed.
- There will be discounts applied to the cart total price. If the customer has just registered to the site, there will be a 2% discount on the first purchase.
- If the customer has a coupon, another 5% discount will be applied on the total price. The coupon code and the percentage amounts may vary.
- If customer’s requested quantity of product exceeds the available stock, then it will be registered as an issue.
- If a product turns out-of-stock, an error will be registered.
Now since the rules are cart and cart item based, we have grouped the rules in two different files.
Few points about cart rules:
- If a product is not available, the inference engine will end up matching more than one rule ‘Is Out-Of Stock’ and ‘Verify Qty’.
- If a custom is new and is interested to buy a product which requires mandatory special registration then again we will end up with more than one rule.
- Most of the rules fire for cart item which not yet ‘PROCESSED’ but there is one rule ‘Add Processed CartItem to Order’ which fires ONLY for items which are PROCESSED.
cartItem.drl:
package com.javacodegeeks.drools; import com.javacodegeeks.drools.Cart; import com.javacodegeeks.drools.CartItem; import com.javacodegeeks.drools.CartStatus; import com.javacodegeeks.drools.Product; import java.util.List; global List<Product> outOfStockProducts; function String pendingItemKey(CartItem cartItem) { return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc(); } //Is it out of stock? rule "Is Out-Of Stock" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0) then System.out.println("\nIs Out-Of Stock Rule"); System.out.println("************************************"); String error = "Can't process as " + $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ; System.out.println(error); $cartItem.setErrors(true); $cartItem.setError(error); $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem); outOfStockProducts.add($cartItem.getProduct()); end rule "Verify Qty" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty()) then System.out.println("\nVerify Qty Rule"); System.out.println("************************************"); String error = "Can't process as only " + $cartItem.getProduct().getAvailableQty() + " of " + $cartItem.getProduct().getDesc() + " are left whereas qty requested is " + $cartItem.getQty(); System.out.println(error); $cartItem.setErrors(true); $cartItem.setError(error); $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem); end rule "If has coupon, 5% discount" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.coupon == 'DISC01') then if (!$cartItem.hasErrors()) { System.out.println("\nIf has coupon, 5% discount Rule"); System.out.println("************************************"); double discount = ((double)$cartItem.getCart().getTotalPrice())*0.05d; System.out.println("Coupon Rule: Process " + $cartItem.getProduct() + ", qty " + $cartItem.getQty() + ", apply discount " + discount); $cartItem.getCart().addDiscount(discount); } end rule "If new, 2% discount" when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.isNew()) then if (!$cartItem.hasErrors()) { System.out.println("\nIf new, 2% discount Rule"); System.out.println("************************************"); double discount = ((double)$cartItem.getCart().getTotalPrice())*0.2d; System.out.println("New Customer Rule: Process " + $cartItem.getProduct() + ", qty " + $cartItem.getQty() + ", apply discount " + discount); $cartItem.getCart().addDiscount(discount); } end rule "Has customer registered for the product?" salience 1 when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, product.isRequiresRegisteration(), !cart.customer.isRegistered(product)) then System.out.println("\nHas customer registered for the product? Rule"); System.out.println("************************************"); String error = "Can't process " + $cartItem.getProduct() + ", as requires registration. Customer not registered for the product!"; System.out.println(error); $cartItem.setErrors(true); $cartItem.setError(error); $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem); end rule "Add Processed CartItem to Order" when $cartItem : CartItem(cartStatus == CartStatus.PROCESSED) then System.out.println("\nAdd Processed CartItem to Order Rule"); System.out.println("************************************"); System.out.println("Add to order " + $cartItem); end
Let’s review cart rules.
- Cart Rules, insert and modify the facts.
- Once all items are processed, cart goes through each one to figure out if they have an issue. If yes, it inserts a new fact ‘PendingItems’ into the working memory. See ‘Print Cart Issues’
- If the items have no issues, those items get marked as ‘PROCESSED’ and the CartItem fact gets updated. This results into the rules getting re-evaluated. See ‘Mark the items processed’
- There is one rule in cartItem rules file which work on the PROCESSED cart item. Once the CartItem fact is updated, this rule comes into picture and creates an order item for the cart item. See ‘Add Processed CartItem to Order’
Here are the cart rules.
cart.drl:
package com.javacodegeeks.drools; import com.javacodegeeks.drools.Cart; import com.javacodegeeks.drools.CartItem; import com.javacodegeeks.drools.CartStatus; rule "Print Cart Issues" salience -1 when $cart : Cart() then if ($cart.hasIssues()) { System.out.println("\nPrint Cart Issues Rule"); System.out.println("************************************"); System.out.println($cart.getCartIssues()); insert($cart.getPendingItems()); } end rule "Print Pending Items" when $pendingItems : PendingItems() then System.out.println("\nPrint Pending Items Rule"); System.out.println("************************************"); for (CartItem cartItem : $pendingItems.getCartItems()) { System.out.println(cartItem); } end rule "Mark the items processed" salience -2 when $cart : Cart() then System.out.println("\nMark the items processed Rule"); System.out.println("************************************"); for (CartItem cartItem : $cart.getCartItems()) { if (cartItem.getCartStatus() != CartStatus.NEW || cartItem.getCartStatus() != CartStatus.PENDING) { cartItem.updateAsProcessed(); System.out.println(cartItem + " is processed"); update(cartItem); } } end
Let’s run the example.
package com.javacodegeeks.drools; import java.util.ArrayList; import java.util.List; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; /** * This is a sample class to launch a rule. */ public class DroolsExpertSystemExample { public static final void main(String[] args) { try { // load up the knowledge base KieServices ks = KieServices.Factory.get(); KieContainer kContainer = ks.getKieClasspathContainer(); KieSession kSession = kContainer.newKieSession("ksession-rules"); Customer customer = Customer.newCustomer("RS"); Product p1 = new Product("Laptop", 15000); Product p2 = new Product("Mobile", 5000); p2.setRequiresRegistration(true); Product p3 = new Product("Books", 2000); Product p4OutOfStock = new Product("TV", 2000); p4OutOfStock.setAvailableQty(0); Product p5 = new Product("Tab", 10000); p5.setAvailableQty(2); customer.addItem(p1, 1); customer.addItem(p2, 2); customer.addItem(p3, 5); customer.setCoupon("DISC01"); List<CartItem> cartItems = customer.getCart().getCartItems(); for (CartItem cartItem: cartItems) { kSession.insert(cartItem); } System.out.println("************* Fire Rules **************"); kSession.fireAllRules(); System.out.println("************************************"); System.out.println("Customer cart\n" + customer); Customer newCustomer = Customer.newCustomer("JOHN01"); newCustomer.addItem(p1, 1); newCustomer.addItem(p2, 2); newCustomer.addItem(p4OutOfStock, 1); newCustomer.addItem(p5, 10); cartItems = newCustomer.getCart().getCartItems(); for (CartItem cartItem: cartItems) { kSession.insert(cartItem); } kSession.insert(newCustomer.getCart()); kSession.setGlobal("outOfStockProducts", new ArrayList<Product>()); System.out.println("************* Fire Rules **************"); kSession.fireAllRules(); System.out.println("************************************"); System.out.println("Customer cart\n" + customer); } catch (Throwable t) { t.printStackTrace(); } } }
Output:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ************* Fire Rules ************** Has customer registered for the product? Rule ************************************ Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! If has coupon, 5% discount Rule ************************************ Coupon Rule: Process product: Books, price: 2000, qty 5, apply discount 1250.0 If has coupon, 5% discount Rule ************************************ Coupon Rule: Process product: Laptop, price: 15000, qty 1, apply discount 1250.0 If new, 2% discount Rule ************************************ New Customer Rule: Process product: Books, price: 2000, qty 5, apply discount 5000.0 If new, 2% discount Rule ************************************ New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 5000.0 ************************************ Customer cart Customer new? true Coupon: DISC01 Product: product: Laptop, price: 15000, qty: 1, processed: false Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! Product: product: Books, price: 2000, qty: 5, processed: false Discount: 12500.0 Total: 25000 Total After Discount: 12500 ************* Fire Rules ************** Has customer registered for the product? Rule ************************************ Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! Is Out-Of Stock Rule ************************************ Can't process as TV is Out-Of-Stock Verify Qty Rule ************************************ Can't process as only 2 of Tab are left whereas qty requested is 10 Verify Qty Rule ************************************ Can't process as only 0 of TV are left whereas qty requested is 1 If new, 2% discount Rule ************************************ New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 3000.0 Print Cart Issues Rule ************************************ JOHN01-TabProduct: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10 JOHN01-MobileProduct: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! JOHN01-TVProduct: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 Print Pending Items Rule ************************************ Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10 Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 Mark the items processed Rule ************************************ Product: product: Laptop, price: 15000, qty: 1, processed: false is processed Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! is processed Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 is processed Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10 is processed Add Processed CartItem to Order Rule ************************************ Add to order Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10 Add Processed CartItem to Order Rule ************************************ Add to order Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 Add Processed CartItem to Order Rule ************************************ Add to order Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! Add Processed CartItem to Order Rule ************************************ Add to order Product: product: Laptop, price: 15000, qty: 1, processed: false ************************************ Customer cart Customer new? true Coupon: DISC01 Product: product: Laptop, price: 15000, qty: 1, processed: false Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! Product: product: Books, price: 2000, qty: 5, processed: false Discount: 12500.0 Total: 25000 Total After Discount: 12500
8. Download the Eclipse Project
This was an example about Drools Expert System.
You can download the full source code of this example here: DroolsExpertSystemExample.zip