Execute business validations using OT Rules
Abstract
OT Rules is a simple rule engine that focuses on execution, validation and business rules composition. One of the main advantages of using OT Rules is that you can define business validations and keep them isolated from business logic so you can add, remove, change or compose these rules and new rules in a simple, flexible way without affecting the business logic. In this article, we will apply these validations, enable, disable and reuse them in different methods and add new validations without changing the business logic methods.
How to create a rule.
Rule creation is a simple process, you just have to create a new class that implements or extends some of the following interfaces or classes:
#
net.sf.opentranquera.rules.Rule
: Base Interface for all the rules.#
net.sf.opentranquera.rules.BusinessRule
: Business rule specific Interface#
net.sf.opentranquera.rules.CompositeRule
: Composite rule specific Interface#
net.sf.opentranquera.rules.AbstractRule
: Can be used in different rule types#
net.sf.opentranquera.rules.AbstractBusinessRule
: Useful for creating business rules# Extend some of the existing rules in OT Rules so enhancing its behavior.
In our example, we will extend
net.sf.opentranquera.rules.AbstractBusinessRule
in order to create different business validation rules.Example application
Let's consider that we have to develop an application that makes an electronic fund transfer between different accounts. We can identify several services:
# Fund transfer from one account to other
# Get the balance of an existing account.
We can identify here business validations that have to be considered when executing these services, for instance, when we are executing an electronic fund transfer or when we get an account balance we must validate that the accounts are valid or exist. To implement these validations, we are going to use OT Rules because:
* Validations can change without changing the business logic
* We could require to disable the validations in some particular environments
* We could add new rules as the business change or evolve
* We need flexibility to compose validations
* We need to reuse the business validation along the project and not rewrite code.
Note: in our example, for simplicity sake, we are going to use an in-memory java.util.Map . the configuration of the example application will be based on SpringFramework.
Creating the rules.
We have identified the following business rules:
# DifferentAccountsRule: checks that both accounts source and destination are different.
# AccountsExistsRule: Validates that both accounts exist. Uses AccountExistsRule to check each account.
This is the rules source code:
public class DifferentAccountRule extends AbstractBusinessRule {
/*
* @see net.sf.opentranquera.rules.Rule#evaluate()
*/
public RuleResult evaluate() {
Transfer transfer = (Transfer) this.getEvaluatedObject();
if( transfer.getCreditAccount().equals(transfer.getDebitAccount()) )
return this.getError("The accounts are equal");
return this.getSuccess();
}
}
public class AccountsExistsRule extends AbstractBusinessRule {
private BusinessRule rule;
/*
* @see net.sf.opentranquera.rules.Rule#evaluate()
*/
public RuleResult evaluate() {
Transfer transfer = (Transfer) this.getEvaluatedObject();
AbstractCompositeRuleResult result = new AbstractCompositeRuleResult(true) {
};
this.rule.setEvaluatedObject(transfer.getDebitAccount());
RuleResult r1 = this.rule.evaluate();
this.rule.setEvaluatedObject(transfer.getCreditAccount());
RuleResult r2 = this.rule.evaluate();
result.addResult( r1 );
result.addResult( r2 );
result.setSuccessful(r1.isSuccessful() && r2.isSuccessful());
return result;
}
public void setRule(BusinessRule rule) {
this.rule = rule;
}
}
public class AccountExistsRule extends AbstractBusinessRule {
private AccountDao dao;
/*
* @see net.sf.opentranquera.rules.Rule#evaluate()
*/
public RuleResult evaluate() {
String account = (String)this.getEvaluatedObject();
// Verify that the account exists.
if(this.dao.getAccount(account) == null)
return super.getError("The account " + account + "does not exist.");
return this.getSuccess();
}
public void setDao(AccountDao dao) {
this.dao = dao;
}
}
Now, we have to configure the rules and the service being used, in this example, SpringFramework (there is also another configuration format using XML, provided out-of-the-box by OT Rules):
<bean id="service" class="net.sf.opentranquera.samples.rules.TransferServiceImpl">
<property name="evaluator" ref="ruleEvaluator"/>
</bean>
<bean id="ruleEvaluator" class="net.sf.opentranquera.rules.RuleEvaluator">
<property name="rules">
<map>
<entry key="transfer"><ref local="transferRule"/></entry>
<entry key="balance"><ref local="accountExistsRule"/></entry>
</map>
</property>
</bean>
<bean id="transferRule" class="net.sf.opentranquera.rules.logical.AndRule">
<constructor-arg index="0">
<list>
<bean class="net.sf.opentranquera.samples.rules.DifferentAccountRule"/>
<bean class="net.sf.opentranquera.samples.rules.AccountsExistsRule">
<property name="rule" ref="accountExistsRule"/>
</bean>
</list>
</constructor-arg>
<constructor-arg index="1" value="true"/>
</bean>
<bean id="accountExistsRule" class="net.sf.opentranquera.samples.rules.AccountExistsRule">
<property name="dao" ref="dao"/>
</bean>
<bean id="dao" class="net.sf.opentranquera.samples.rules.AccountDao"/>
As we can see iin the example, an evaluator gets injected in the service. The evaluator is
net.sf.opentranquera.rules.RuleEvaluator
typed. The ruleEvaluator is configured like a Spring-Bean, being injected the different rules that it can evaluate, in the example two: Transfer and Balance.Each rule can be, like the transfer rule, a CompositeRule, that contains other rules inside. Now, from the service implementation or with an interceptor, you can call the evaluate in the RuleEvaluator to execute the rules. This example is inside the method, but using an interceptor is recommended:
public boolean transfer(Transfer transfer) throws TransferException {
this.evaluator.setEvaluatedObject(transfer);
RuleResult result = this.evaluator.evaluateRule("transfer");
if(!result.isSuccessful())
throw new TransferException(result.getMessages());
// TODO logic ..
return true;
}
Add a new validation using composition.
Now, the features of OT Rules come to light while adding and modifying new rules and execute business validations without changing the source code (in this case, the service implementation). We are going to add a new business rule that checks if there is enough funds in the source account. First of all, we create the new java rule class:
public class DebitAccountRule extends AbstractBusinessRule {
/*
* @see net.sf.opentranquera.rules.Rule#evaluate()
*/
public RuleResult evaluate() {
Transfer transfer = (Transfer) this.getEvaluatedObject();
// Get the balance of the account
// Checks if ther is enough funds to execute the transfer.
return this.getSuccess();
}
}
In this case, we have an mock implementation just to prove how OT rules works and is configured. Now we add the new rule in the configuration file:
<bean id="transferRule" class="net.sf.opentranquera.rules.logical.AndRule">
<constructor-arg index="0">
<list>
<bean class="net.sf.opentranquera.samples.rules.DifferentAccountRule"/>
<bean class="net.sf.opentranquera.samples.rules.AccountsExistsRule">
<property name="rule" ref="accountExistsRule"/>
</bean>
<bean class="net.sf.opentranquera.samples.rules.DebitAccountRule"/>
</list>
</constructor-arg>
<constructor-arg index="1" value="true"/>
</bean>
That's all what you have to do in order to add a new validation rule to the method execution. As we can see, nothing is changed in the TransferServiceImpl source code.
Rules reuse
OT Rules allows to reuse the rules in different objects or RuleEvaluators, for instance in the previous example we are going to reuse the rule
net.sf.opentranquera.samples.rules.AccountExistsRule
, in the ruleEvaluator bean asigning the execution of the rule "balance" and in transferRule as one of the included rules:
<bean id="ruleEvaluator" class="net.sf.opentranquera.rules.RuleEvaluator">
<property name="rules">
<map>
<entry key="transfer"><ref local="transferRule"/></entry>
<entry key="balance"><ref local="accountExistsRule"/></entry>
</map>
</property>
</bean>
<bean id="transferRule" class="net.sf.opentranquera.rules.logical.AndRule">
<constructor-arg index="0">
<list>
<bean class="net.sf.opentranquera.samples.rules.DifferentAccountRule"/>
<bean class="net.sf.opentranquera.samples.rules.AccountsExistsRule">
<property name="rule" ref="accountExistsRule"/>
</bean>
<bean class="net.sf.opentranquera.samples.rules.DebitAccountRule"/>
</list>
</constructor-arg>
<constructor-arg index="1" value="true"/>
</bean>
More Rules
OT Rules has a built-in rule set to make the development easier:
*
AndRule
: Executes the AND operation between several rules. Can use a short circuit if needed.*
OrRule
: Executes the OR operation between several rules. Also can use a short circuit if needed.*
XorRule
: Executes XOR between several rules.*
NotRule
: Applies NOT to the result of the rule execution*
IfRule
: Executes one or other rule using as a condition another rule.Also a set of
RuleResults
are provided, as follows:*
SimpleResult
: Basic functionality for a RuleResult.*
TrueResult
: Is a RuleResult that returns true.*
FalseResult
: Is a RuleResult that returns false.Finally, as mentioned earlier, OT Rules can be configured using different mechanisms:
* Spring: as used in this example
* API: You can create rule with java code, using a FluentAPI that enables the creation of rules in the java programming language, for example:
FluentRule fir = new FluentRule("test");
fir.rule(new TrueRule()).and(new TrueRule()).or(new FalseRule()).not();
RuleEvaluator evaluator = FluentInterfaceRuleEvaluatorBuilder.createRuleEvaluator(fir);
RuleResult result = evaluator.evaluateRule("test");
* XML: A XML format that has an Eclipse plugin to edit. Here is an example:
<rules>
<rule name="test.single.rule"
ruleClass="net.sf.opentranquera.rules.HelloWorldRule" />
<rule name="test.param.rule"
ruleClass="net.sf.opentranquera.rules.WordLengthRule">
<param name="length" value="11" />
</rule>
<rule name="test.and.rule"
ruleClass="net.sf.opentranquera.rules.logical.AndRule">
<rule ruleClass="net.sf.opentranquera.rules.HelloWorldRule" />
<rule ruleClass="net.sf.opentranquera.rules.WordLengthRule">
<param name="length" value="14" />
</rule>
</rule>
<rule name="test.not.rule"
ruleClass="net.sf.opentranquera.rules.logical.NotRule">
<rule ruleClass="net.sf.opentranquera.rules.support.FalseRule" />
</rule>
<rule name="test.negate.rule"
ruleClass="net.sf.opentranquera.rules.support.TrueRule"
modifier="negate" />
<rule name="test.and.ref"
ruleClass="net.sf.opentranquera.rules.logical.OrRule"
shortCircuit="false">
<rule ref="test.single.rule" />
<rule ref="test.param.rule" />
</rule>
</rules>
Summary:
In this article, we introduced OT Rules and walked through a simple example using OT Rules to execute business validations. We've also explained the different built-in handy rules and how to configure the rule set to perform validations.
No hay comentarios:
Publicar un comentario