- It makes the business rules implementations more managable.
- You adhere to the "Don't Repeat Yourself" principle (DRY)
- A change in a rule can be immediately consumed by all the applications.
- All of the above implies that your applications are less error prone.
How?
Every business rule can be expressed as a formula, and some business rules are composites, which can be expresssed as composite formulas. In turn, each formula can be expressed as a groovy closure, and you can have composite groovy closures too. Finally, yo can easily store your closures as strings and evaluate them at runtime: groovy magic ! =)
Example
Suppose we have a rule which sums the result of making some calculation on 2 properties (a and b) of every element in a collection, then applies a factor an returns the result. In groovy we can express this rule as the following closure:
// Rule A
{ v, rule, factor -> def total = 0; v.each { total += rule( it.a, it.b ) } ; total*factor }
Now, one variant of the nested rule (closure), which takes any two parameters, could be something like:
// Rule B
{ x, y -> Math.sqrt( x.power(y) ) }
Both rules can be stored as strings in a database, then you retrieve, assemble and execute them this way:
def ruleACode = ruleDAO.findRule('RULE_A')
def ruleA = Eval.me(ruleACode)
def ruleBCode = ruleDAO.findRule('RULE_B')
def ruleB = Eval.me(ruleBCode)
def entities =
[ new MyBean(a:5, b:2), new MyBean(a:2, b:4) ]
assert ruleA(entities, ruleB, 0.5) == 4.5
In the code above, first I get the string representation of both rules and convert them to individual closures, which is achieved with Eval.me(). Eval is a convenience class designed to easily execute groovy scripts from plain old java code. I recommend reading the javadoc.The list 'entities' can be replaced with any collection of objects with properties of name 'a and 'b'.
Finally I just call the closure (which implements Rule A) passing the list, the second closure (which implements Rule B) and the factor as parameters.
Discussion
You may argue that you can achieve business rules implementation management through proper component design, modularization and management. And, although I think it's the only way to go on some scenarios, I see various inconveniences in it when compared with the groovy+closure approach:
- Code Bloating
Did you notice how easy is to compose and execute closures? This is because a closure resembles a function, which is a vastly simpler concept than a class. Implementing rules with classes packaged in different jars can easily lead you to piles of complex boilerplate code to maintain and tons of related build and dependency management scripts and configuration files. - Incompatible components
One drawback of highly modularized systems, where each module can have a life cycle of it's own, is how easy is to get into component impedance headaches (I believe most of you know what I am talking about) - Separation of rule definition and implementation
If you separate them, you need extra work (code, process, practice, people) to get them in sync.
Final Thoughts
You don't even need your whole team to learn groovy stuff. Here you have the execution of the same rules from pure Java code:
public void testComposedRules() throws Exception {
String ruleACode = ruleDAO.findRule("RULE_A");
Closure ruleA = (Closure) Eval.me(ruleACode);
String ruleBCode = ruleDAO.findRule("RULE_B");
Closure ruleB = (Closure) Eval.me(ruleBCode) ;
List entities = new ArrayList() {{
add( new MyBean() {{ setA(5); setB(2); }} );
add( new MyBean() {{ setA(2); setB(4); }} );
}};
assertEquals(
ruleA.call( new Object[] {entities, ruleB, 0.5 }), 4.5 );
} You just need the groovy jar. Your class dependencies will limit to the Eval and Closure classes.Let me know how it works for you! =)
The idea is great and what I have been looking for.
ReplyDeleteBut I am a new Groovy admirer.. and could not understand your RuleA and RuleB.
Plaese give a bit more explanation how to interpret that Rule in plain english.
The example rules are not really meaningful. Rule A just returns the result of multiplying a factor by the sum of applying rule B to every element in collection 'v'. From the point of view of rule A, rule B can be anything taking an object with properties 'a' and 'b' and returning a number. In this particular case, rule B just returns the square root of x^y. From the point of view of rule B, 'x' and 'y' can be any number, but while executing this example, they take the value of 'a' and 'b' properties from the objects given by rule A.
ReplyDelete