BPM Events tutorial : Using rules to synchronize Business Process Server processes
Using rules to synchronize Business Process Server processes
Let us return to the discount policy rule described in Using rules to control Business Process Server processes . When writing rules that cooperate with processes, it is often necessary to take additional precautions to make sure that the rules and the process are well synchronized. The discount policy rule that we wrote earlier may not properly control the Purchase Order process in some cases. Indeed, you must design the purchase order process so that it waits for the discount decision before resuming further, that is, before reaching the point where it actually calculates the customer’s bill. The Application Developer’s Guide explains how a process instance can wait for a flag (dataslot) to take a certain value, before proceeding further.
You may design the Purchase Order process so that it waits for an explicit signal from the rules before proceeding further (instead of only waiting for the Discount value to change). In that case, the discount rule must generate this additional signal. This is done by adding the action: order.flagOn("continue") after the discount change.
Note: The business process instance (identified by the variable order) has defined an attribute named "continue", with initial value to false, and waits until this value is true.
The original bold_discount rule now becomes:
rule bold_discount
activated by
event1 of BP Server::PI_ACTIVATED {PROCESSTEMPLATE NAME : "CustomerRequest"}
if
(event1.totalamount >= 500)
then {
val order = BLsession().getPI(event1);
order.setDataslotValue("Discount", 10);
order.flagOn("continue");
}
However, we must also make sure that the PurchaseOrder process resumes, even when the discount does not apply. Remember that each process instance waits for a decision from the rule, whether it is an acceptance or a rejection. The rule above only handles the acceptance case: it subsequently updates the discount rate, and after this tells the process instance to resume its course: (order.flagOn("continue")).
We must also tell the process instance to resume its course when the discount is declined. For this, we can write a second rule, named here no_discount, that also handles the non-discount case:
rule no_discount
activated by event1 of BP Server::PI_ACTIVATED {PROCESSTEMPLATE NAME :
"CustomerRequest"}
if
(event1.totalamount < 500)
then {
val order = BLsession().getPI(event1);
order.flagOn("continue");
}
For each event of type BP Server::PI_ACTIVATED, we are now sure that either the bold_discount or no_discount rule will be successfully triggered, and that either one of these rules will release or unblock the waiting process instance that triggered it.
There is, however, a simpler way to handle the discount policy. The current version of Business Process Server provides a single rule to handle both the discount case and the no-discount case, which is displayed as follows:
whether_discount_or_not,
This rule always triggers successfully, that is, executes its action each time it is triggered. It always executes the unblocking action order.flagOn("continue") for each Purchase Order activation event, whether the discount applies or not.
rule whether_discount_or_not
activated by evt1 of BP Server::PI_ACTIVATED {PROCESSTEMPLATE NAME : "CustomerRequest"}
if (evt1.PROCESSTEMPLATENAME = "CustomerRequest")
{
val order = BLsession().getPI(evt1);
if ( evt1.totalamount >= 500) order.setDataslotValue("Discount", 10);
order.flagOn("continue");
}
Note: The discount test is moved out of the main if clause of the rule, and into the action part (then clause), which also contain if-then conditions.
The whether_discount_or_not rule has the following behavior:
It is triggered each time an order is submitted, on reception of an event PI_Activated of type BP Server.
It accesses the process instance handle (order), and tests its totalamount attribute.
If the amount is greater than $500, then it updates the discount attribute associated with this order process to 10 percent; otherwise it leaves this attribute at whatever value it was set initially.
The rule then signals the process to resume its course, even if the discount did not apply.
By using rules, you can control a process in a more sophisticated way. For example, we can define an earlybird_discount rule that decides to enable a rebate only for the first 1000 customers of each region. In that case, we will use the previous infopad ReqStats which already monitors the customer requests. By adding a condition to the rule that tests the count value in this infopad, we filter out any discount candidate once the count for this region is above 1000 requests.
rule earlybird_discount
activated by
evt1 of BP Server::PI_ACTIVATED {PROCESSTEMPLATE NAME :
"CustomerRequest"}
{
val order = BLsession().getPI(evt1);
if (( evt1.totalamount >= 500) and (evt1.reqtype = "ProductOrder") and
(ReqStats["ProductOrder"][ evt1.region].count < 1001))
order.setDataslotValue("Discount", 10);
order.flagOn("continue");
}
In the earlybird_discount rule, we moved all the conditions out of the if clause, and put them inside the action part of the rule. The result is that the rule is always triggered for any event of type BP Server::PI_ACTIVATED and therefore, executes all the actions, except discount update, which is now directly subject to the restrictions, as expressed in the conditional statement of the action part of the rule.
A final improvement on this rule allows for updating the customer count at the same time we do the discount. For this, we add the increment statement after the setDataslotValue statement. The conditional statement in the action part becomes:
...
if ( (toInt(evt1.totalamount) >= 500)
and (evt1.reqtype = "ProductOrder")
and (ReqStats["ProductOrder"][ evt1.region].count < 1001))
{ order.setDataslotValue("Discount", 10);
ReqStats["ProductOrder"][ evt1.region].count++ ;}
...