0

I'm working on a drools project where every .drl file is created dynamically from velocity template. I've written every calculation and operations to be performed after successfull condition check in the then of .drl file itself.

Lets assume a simple condition

rule "Rule %"
no-loop
salience 10

when
    $var: Map( this["Key"] == "SomeValue" )
then
    $var.put("Discount%", Do-SOME-%-CALCULATION AND PUT IT HERE)
end

rule "Rule Amt"
no-loop
salience 9

when
    $var: Map( this["Key"] == "SomeValue" )
then
    $var.put("DiscountAmt", Do-SOME-Amt-CALCULATION AND PUT IT HERE)
end

rule "Rule % Amt"
no-loop
salience 8

when
    $var: Map( this["Key"] == "SomeValue" )
then
    $var.put("Discount%", Do-SOME-%-CALCULATION AND PUT IT HERE)
    $var.put("DiscountAmt", Do-SOME-Amt-CALCULATION AND PUT IT HERE)
end

To form these kind of DRL files, I've formed the velocity template like this

#set($d = "$")

rule "$rule.name $rule.type"
no-loop
salience $rule.priority

when
    ${d}var: Map( this["$rule.keyName"] == "$rule.keyValue" )
then
    #if( $rule.type == "%" )
        ${d}var.put("Discount%", CODE-FOR-%-CALCULATION);
    #elseif( $rule.type == "Amt" )
        ${d}var.put("DiscountAmt", CODE-FOR-AMT-CALCULATION);
    #elseif( $rule.type == "% Amt" )
        ${d}var.put("Discount%", CODE-FOR-%-CALCULATION);
        ${d}var.put("DiscountAmt", CODE-FOR-AMT-CALCULATION);
    #end

I know that last #elseif( $rule.type == "% Amt" ) can be eliminated by utilising the first two if and elseif by putting || $rule.type == "% Amt" in both those conditions. But this is just an example.

Just assume I dont have any choice to simplify those 3 condition to 2 conditions but to repeat my code itself. Believe me, I've a lot more calculations and I've to repeat those codes multiple types in the velocity code. This becomes a mess overall. Because, If I've to make a small change in the formula, I've to make changes in all those repetitive codes in velocity template which will definitely lead to human errors if we've missed out the change in a repetitive code.

Thats why I've thought to wrote those formulas and calculations in Java class and simply make calls to that class in .drl. So .drl and .vm

In .drl

import com.package.util.RuleUtil;

rule "Rule %"
no-loop
salience 10

when
    $ruleUtil: RuleUtil()
    $var: Map( this["Key"] == "SomeValue" )
then
    $ruleUtil.discountPer($var);
end

rule "Rule Amt"
no-loop
salience 9

when
    $ruleUtil: RuleUtil()
    $var: Map( this["Key"] == "SomeValue" )
then
    $ruleUtil.discountAmt($var);
end

rule "Rule % Amt"
no-loop
salience 8

when
    $ruleUtil: RuleUtil()
    $var: Map( this["Key"] == "SomeValue" )
then
    $ruleUtil.discountPer($var);
    $ruleUtil.discountAmt($var);
end

In .vm

#set($d = "$")

rule "$rule.name $rule.type"
no-loop
salience $rule.priority

when
    ${d}ruleUtil: RuleUtil()
    ${d}var: Map( this["$rule.keyName"] == "$rule.keyValue" )
then
    #if( $rule.type == "%" )
        ${d}ruleUtil.discountPer(${d}var);
    #elseif( $rule.type == "Amt" )
        ${d}ruleUtil.discountAmt(${d}var);
    #elseif( $rule.type == "% Amt" )
        ${d}ruleUtil.discountPer(${d}var);
        ${d}ruleUtil.discountAmt(${d}var);
    #end

If you want to know how many times I've repeated that discount formula code, I would say a minimum of 10 times and there involves a lot more repetitions for multiple type of discount calculations. TBH, I'm getting irritated when looking at the .vm code. Because of all those code repetitions and poor maintainability in long run. I'm wondering whether to stick with the current code (Current vm code which involves multiple code repetions and hard to maintian) Or should I move all those formula calculations to Java method by implementing my proposed structure. I don't know how much it'll affect my .drl file performance. Any suggestions will be appreciated.

Note: I prefer code maintainability the most. I don't want someone to break his head after he has taken over this project. Ofcourse, I too don't want to break my head while making any small change.

The Coder
  • 2,562
  • 5
  • 33
  • 62

1 Answers1

1

You haven't made the case for using templates in the first place. It could be that you have chosen the second best approach to start with.

That said, my opinion (based on using templates a lot, although not for generating DRL) is to make template logic as simple as possible. I would not put any formulas into the template - stick with the (static) RuleUtil methods.

Note that you don't need to have RuleUtil as a fact - you can reference its static methods just by importing the class.

Even the simple if-elsif-statement in the last tmeplate is something I'd try to avoid. Given that rule type could be one of Amount, Percent and PercentAmount, you could write

rule "$rule.name $rule.type"
when
    ${d}var: Map( this["$rule.keyName"] == "$rule.keyValue" )
then
    ${d}ruleUtil.discount$rule.type(${d}var);
#end

I have omitted salience and no-loop: I don't see the reason for the second and the first is almost always a bad idea for rules like these.

Finally: I dislike Map as a fact - but perhaps there's an excellent reason to not use a bean-style fact class.

laune
  • 31,114
  • 3
  • 29
  • 42
  • Thanks for your thoughts. I have a small doubt. My drools were formed dynamically. So I dont know how many facts could be present in `when`.. Like, I may have `$var: Map() , $var1: Map(), $var2: Map() and so on..` Is there any way I can get the list of facts that declared in `when`, I mean I want the list of all facts to be available inside `then`. – The Coder Jul 20 '15 at 21:21
  • If the generator is capable of producing the list for the LHS it surely should be able of doing the same on the RHS. If you need them in a java.util.List, you have all of Java to do so. But it seems that this is another question. – laune Jul 21 '15 at 05:09