提问者:小点点

Drools规则被执行的次数超过预期


我是Drools的新手,正在跟随这本书:掌握JBoss Drools 6

第2章展示了一个示例,我们创建了一个客户订单,根据订单大小对客户进行分类,提供折扣并生成优惠券

该示例提到:

  • 一个客户
  • 客户有一个包含5个项目的订单,因此被归类为SILVER客户
  • 每件商品售价超过500,因此归类为HIGH_RANGE商品
  • 客户是SILVER客户,因此创建了优惠券
  • 为每位SILVER订单超过2件商品的客户提供10%的折扣

以下是Drools规则:

add-discount. drl

rule "High Range order - 10% discount"
    when
        $o: Order($lines: orderLines, orderLines.size >= 2, $customer: customer, discount == null)
        $c: Customer(category == Customer.Category.SILVER, this == $customer)
        forall(
            OrderLine(this memberOf $lines, $item: item)
            Item(this == $item, category == Item.Category.HIGH_RANGE)
        )
    then
        modify($o){
            setDiscount(new Discount(10.0))
        }
end

分类-客户-规则. dr l

rule "Classify Customer by order size"
    when
        $o: Order( orderLines.size >= 5, $customer: customer )
        $c: Customer(this == $customer, category == Customer.Category.NA)
    then
        modify($c){
            setCategory(Customer.Category.SILVER)
        }
end

分类-项目-规则. dr l

rule "Classify Item - Low Range"
    when
        $i: Item(cost < 200, category == Category.NA)
    then
        $i.setCategory(Category.LOW_RANGE);
        update($i);
end

rule "Classify Item - Mid Range"
    when
        $i: Item(cost > 200 && cost < 500, category == Category.NA)
    then
        $i.setCategory(Category.MID_RANGE);
        update($i);
end

rule "Classify Item - High Range"
    when
        $i: Item(cost >= 500, category == Category.NA)
    then
        $i.setCategory(Category.HIGH_RANGE);
        update($i);
end

优惠券创建

rule "Create Coupons for silver customer"
    when
        $o: Order($customer: customer)
        $c: Customer(this == $customer, category == Customer.Category.SILVER)
    then
        Coupon x = new Coupon($c, $o, Coupon.CouponType.POINTS);
        System.out.println(x);
        insert(x);
end

执行的规则数量应该是8个。5个商品分类(5个规则)1个客户分类(1个规则)1个优惠添加(1个规则)1个优惠券创建(1个规则)

但是实际触发的规则数是9。这是我用来检查的测试。

@Test
    void orderDiscountTest() {
        KieSession kieSession = Util.getDefaultKieSession();
        Order o = ModelFactory.getOrderWithFiveHighRangeItems();

        kieSession.insert(o.getCustomer());
        kieSession.insert(o.getOrderLines().get(0));
        kieSession.insert(o.getOrderLines().get(1));
        kieSession.insert(o.getOrderLines().get(2));
        kieSession.insert(o.getOrderLines().get(3));
        kieSession.insert(o.getOrderLines().get(4));
        kieSession.insert(o.getOrderLines().get(0).getItem());
        kieSession.insert(o.getOrderLines().get(1).getItem());
        kieSession.insert(o.getOrderLines().get(2).getItem());
        kieSession.insert(o.getOrderLines().get(3).getItem());
        kieSession.insert(o.getOrderLines().get(4).getItem());
        kieSession.insert(o);

        int fired = kieSession.fireAllRules();

        assertEquals(8, fired);
        assertEquals(Customer.Category.SILVER, o.getCustomer().getCategory());
        assertNotNull(o.getDiscount());
        assertEquals(10.0, o.getDiscount().getPercentage());
        assertEquals(Item.Category.HIGH_RANGE, o.getOrderLines().get(0).getItem().getCategory());
        assertEquals(Item.Category.HIGH_RANGE, o.getOrderLines().get(1).getItem().getCategory());
        assertEquals(Item.Category.HIGH_RANGE, o.getOrderLines().get(2).getItem().getCategory());
        assertEquals(Item.Category.HIGH_RANGE, o.getOrderLines().get(3).getItem().getCategory());
        assertEquals(Item.Category.HIGH_RANGE, o.getOrderLines().get(4).getItem().getCategory());

        Collection<Coupon> coupons = Util.getFactsFromSession(kieSession, Coupon.class);
        coupons.forEach(x -> System.out.println(x));
        assertEquals(1, coupons.size());
    }

优惠券创建规则触发了两次,不明白为什么,我这样查看了触发规则后创建的优惠券数量:

Collection<Coupon> coupons = Util.getFactsFromSession(kieSession, Coupon.class);
coupons.forEach(x -> System.out.println(x));

并且我在优惠券创建规则中也有打印语句。

这就是我得到的:

12:54:47.607 [main] DEBUG org.drools.core.impl.KnowledgeBaseImpl - Starting Engine in PHREAK mode
12:54:47.730 [main] DEBUG org.drools.core.common.DefaultAgenda - State was INACTIVE is nw FIRING_ALL_RULES
12:54:47.732 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.795 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
Coupon created com.example.droolstut.model.Coupon@b14cb989
12:54:47.797 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.797 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.799 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.806 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
Coupon created com.example.droolstut.model.Coupon@424f842a
12:54:47.806 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.806 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:54:47.806 [main] DEBUG org.drools.core.common.DefaultAgenda - State was FIRING_ALL_RULES is nw HALTING
12:54:47.806 [main] DEBUG org.drools.core.common.DefaultAgenda - State was HALTING is nw INACTIVE
Coupon found: com.example.droolstut.model.Coupon@424f842a
Coupon found: com.example.droolstut.model.Coupon@424f842a

所以,看起来规则执行了两次,但执行后只找到了第二张优惠券。

但是,当我在调试模式下运行测试时,优惠券规则只执行一次,并且触发的规则总数也是8个。

我错过什么了吗?我很感激任何帮助。

编辑1:

我在添加not(Coupon(…))条件后打印了订单和客户。项目对象看起来不同。

Order(orderId=1, date=Thu Jan 01 05:30:00 IST 1970, customer=Customer(customerId=1, age=40, name=John Doe, email=johndoe@example.com, category=SILVER), orderLines=[OrderLine(item=com.example.droolstut.model.Item@38c70399, quantity=1), OrderLine(item=com.example.droolstut.model.Item@5245e3d4, quantity=1), OrderLine(item=com.example.droolstut.model.Item@6bc4c40f, quantity=1), OrderLine(item=com.example.droolstut.model.Item@8543a44a, quantity=1), OrderLine(item=com.example.droolstut.model.Item@9ec28485, quantity=1)], state=PENDING, discount=null) Customer(customerId=1, age=40, name=John Doe, email=johndoe@example.com, category=SILVER)
com.example.droolstut.model.Coupon@2753dad2
12:16:02.399 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:16:02.400 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:16:02.402 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
12:16:02.408 [main] DEBUG org.drools.core.common.DefaultAgenda - Fire Loop
Order(orderId=1, date=Thu Jan 01 05:30:00 IST 1970, customer=Customer(customerId=1, age=40, name=John Doe, email=johndoe@example.com, category=SILVER), orderLines=[OrderLine(item=com.example.droolstut.model.Item@5dc27ea9, quantity=1), OrderLine(item=com.example.droolstut.model.Item@77415ee4, quantity=1), OrderLine(item=com.example.droolstut.model.Item@90c03f1f, quantity=1), OrderLine(item=com.example.droolstut.model.Item@aa3f1f5a, quantity=1), OrderLine(item=com.example.droolstut.model.Item@c3bdff95, quantity=1)], state=PENDING, discount=Discount(percentage=10.0)) Customer(customerId=1, age=40, name=John Doe, email=johndoe@example.com, category=SILVER)
com.example.droolstut.model.Coupon@10e5ecb2

编辑2:

我正在为getter/setter/Equals使用lombok

@Override
    public int hashCode() {
        int hash = 3;
        hash = 17 * hash + Objects.hashCode(this.orderId);
        hash = 17 * hash + Objects.hashCode(this.date);
        hash = 17 * hash + Objects.hashCode(this.customer);
        hash = 17 * hash + Objects.hashCode(this.orderLines);
        hash = 17 * hash + Objects.hashCode(this.state);
        hash = 17 * hash + Objects.hashCode(this.discount);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Order other = (Order) obj;
        if (!Objects.equals(this.orderId, other.orderId)) {
            return false;
        }
        if (!Objects.equals(this.date, other.date)) {
            return false;
        }
        if (!Objects.equals(this.customer, other.customer)) {
            return false;
        }
        if (!Objects.equals(this.orderLines, other.orderLines)) {
            return false;
        }
        if (this.state != other.state) {
            return false;
        }
        if (!Objects.equals(this.discount, other.discount)) {
            return false;
        }
        return true;
    }

共1个答案

匿名用户

当一本关于Drools的书出版时,它已经过时了。Drools有很棒的留档,你应该把它作为你的主要来源,而不是一本关于Drools旧版本的旧书。(Drools的当前版本是Drools 8。你的书是为Drools 6写的。)

我建议人们不要使用书籍的另一个原因是因为它们往往很糟糕。这个也不例外

你触发比你想象的更多规则的原因是,“高范围订单-10%折扣”强制重新评估工作记忆,而“为白银客户创建优惠券”没有任何东西可以阻止它多次触发。

“High Range…”调用“修改”,它会重新触发所有带有修改项目的规则。由于“创建优惠券…”仍然有效,它将触发两次。(它会触发一次,然后一旦“High Range…”强制重新评估工作内存,它将触发第二次。)您还会看到工作内存中的优惠券数量是您预期的两倍——将有2张优惠券而不是1张,因为您两次触发优惠券规则。

为了防止这种情况发生,您需要使“创建优惠券…”不能有效触发两次。(也就是说,一旦触发,就不能再次触发。)您可以这样做,例如,不允许两张相同的优惠券。或者您可以只允许一张优惠券。取决于您的用例。

一种方法可能是:

rule "Create Coupons for silver customer"
when
  $o: Order($customer: customer)
  $c: Customer(category == Customer.Category.SILVER)

  // don't fire if this coupon already exists; I guessed on the variable names
  not( Coupon( customer == $c, order == $o, type == Coupon.CouponType.POINTS) )
then
  Coupon x = new Coupon($c, $o, Coupon.CouponType.POINTS);
  insert(x);
end

在现实世界中,你希望你的规则保持简单。不要把整个厨房水槽粘在一个KieBase中。单一责任原则是这里的关键。你的“物品分类”规则应该与你的客户分类规则分开,应该与你的折扣规则分开。

调用update也是让自己陷入无限循环并大大增加内存/cpu开销的好方法。在我的上一家公司,它不仅被认为是代码异味和不良做法,而且实际上被彻底禁止(我们在代码审查中检查过它。)