jueves, 30 de noviembre de 2006

OT-Rules

Ejecutar validaciones de negocio utilizando OT Rules 1.0.1



Abstract
OT Rules es un simple rule engine orientado a la ejecución, validación y composición de reglas de negocio.
Una de las principales ventajas de utilizar OT-Rules es poder definir validaciones de negocio y mantenerlas separadas de la lógica de negocio en sí de forma tal que permita agregar, quitar, modificar y componer estas y nuevas reglas en una manera simple, flexible, mantenible y que no afecte la lógica de negocios de nuestra aplicación. En este articulo se verá como aplicar estas validaciones, habilitarlas, deshabilitarlas, reutilizarlas en diferentes métodos y agregar nuevas validaciones sin afectar al ódigo de los métodos de negocio.

Como crear una rule
La creación de una regla es un proceso simple, lo único que se debe hacer es crear una nueva clase que implemente o extienda de alguna de las siguientes interfaces o clases:
- net.sf.opentranquera.rules.Rule: Interface base para todas las reglas
- net.sf.opentranquera.rules.BusinessRule: Interface apropiada para reglas de negocio
- net.sf.opentranquera.rules.CompositeRule: Interface apropiada para reglar que se compongan de otras reglas
- net.sf.opentranquera.rules.AbstractRule: Clase conveniente para diferentes tipos de reglas
- net.sf.opentranquera.rules.AbstractBusinessRule: Clase conveniente para reglas de negocio
- Heredar de alguna rule existente dentro de OT Rules de forma de extender su comportamiento.

Una vez tomada la decisión de cual interface o clase extender se deben implementar los métodos requeridos. En nuestro ejemplo extenderemos de net.sf.opentranquera.rules.AbstractBusinessRule para crear las diferentes reglas que ejecutaran las diferentes validaciones de negocio.

Aplicación de ejemplo
Imaginemos que tenemos que desarrollar una aplicación que transfiera dinero entre diferentes cuentas. Podemos identificar varios servicios:
a) Transferir de una cuenta a otra.
b) Obtener el saldo de una cuenta en particular.

Existen validaciones de negocio que deben tenerse en cuenta a la hora de ejecutar el código de estos servicios, por ejemplo, cuando hacemos una transferencia o cuando obtenemos el saldo debemos validar que las cuentas existan. Para implementar estas validaciones utilizaremos OT-Rules por las siguientes razones:
1- Porque las validaciones pueden cambiar sin que cambie la lógica de negocios.
2- Porque se puede necesitar deshabilitar las validaciones en determinados ambientes o para un caso de prueba en particular.
3- Porque seguramente abra nuevas validaciones que ir incorporando a medida que el desarrollo crece.
4- Porque se requiere flexibilidad para componer validaciones.
5- Porque es necesario reutilizar validaciones de negocio sin repetir código.

Notas:
En nuestro caso para no complicar el desarrollo de la aplicación utilizaremos un java.util.Map en memoria.
La configuración de nuestra aplicación de ejemplo estara basada en Spring Framework.


Crear las rules
Identificamos las siguientes reglas de negocio:
DifferentAccountsRule: Verifica que las cuentas sean distintas.
AccountsExistsRule: Verifica que las ambas cuentas (débito y crédito) existan. Utiliza AccountExistsRule para verifica la existencia por separado de las cuentas.
Veremos el código de las Rules:

public class DifferentAccountRule extends AbstractBusinessRule {

/* (non-Javadoc)
* @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 equals");
return this.getSuccess();
}

}


public class AccountsExistsRule extends AbstractBusinessRule {

private BusinessRule rule;

/*
* (non-Javadoc)
*
* @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;

/* (non-Javadoc)
* @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;
}
}

Ahora configuramos las rules y el servicio utilizando, en este caso, SpringFramework (tambièn podrìmos utilizar el formato XML que provee 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"/>

Como vemos, al servicio se le inyecta un ''evaluator ''que es del tipo net.sf.opentranquera.rules.RuleEvaluator. El ruleEvaluator se configura como Spring-bean inyectándole las diferentes rules que puede evaluar, en este caso dos, transfer y balance.
Cada una de estas es una Rule que puede ser, como es el caso de transfer, una CompositeRule que contiene a su vez otras rules dentro.
Ahora solo queda desde el servicio (o utilizando algún tipo de interceptor) invocar al ''evaluator ''para evaluar las reglas configuradas.

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;
}


Agregar una nueva validación utilizando composición.
Ahora veremos el potencial de OT-Rules para modificar/agregar nuevas reglas y ejecutar validaciones de negocio sin afectar el código fuente (en este caso del servicio). Agregaremos una nueva regla de negocio que verifique que haya dinero suficiente en la cuenta débito. Para ello primero creamos la nueva regla como una nueva clase Java:

public class DebitAccountRule extends AbstractBusinessRule {

/* (non-Javadoc)
* @see net.sf.opentranquera.rules.Rule#evaluate()
*/
public RuleResult evaluate() {
Transfer transfer = (Transfer) this.getEvaluatedObject();
// Obtener la cuenta debito y la cantidad de dinero disponible
// Verificar que haya dinero suficiente para cubrir la trasferencia.

return this.getSuccess();
}

}

En este caso tenemos una implementacion dummy con el solo echo de mostrar el funcionamiento y configuracion de OT-Rules.
Ahora agregamos esta nueva rule en la configuracion modificando el bean transferRule:

<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>

Así de simple es agregar nuevas reglas o modificar/reemplazar reglas existentes. En este caso no cambia el código de la clase TransferServiceImpl.

Reusar rules
OT-Rules también permite reusar reglas en diferentes objetos o RuleEvaluators, por ejemplo en el caso anterior reutilizamos la regla net.sf.opentranquera.samples.rules.AccountExistsRule, en el bean ruleEvaluator asignando la ejecución de la regla "balance" y en "transferRule" como una de sus reglas incluidas.

<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>


Mas Rules
OT-Rules tiene un conjunto de reglas preconstruidas para facilitar el desarrollo y configuración de reglas de negocios y validaciones:
* AndRule: Ejecuta la operación AND entre varias rules. Puede ser short circuit o no.
* OrRule: Ejecuta la operación OR entre varias rules. Puede ser short circuit o no.
* XorRule: Ejecuta la operación XORentre varias rules.
* NotRule: Niega la ejecución de una rule
* IfRule: Ejecuta una o otra rule en base al resultado devuelto por otra rule.

Además provee un conjunto de RuleResults como ser:
* SimpleResult: Provee funcionalidad básica para un RuleResult.
* TrueResult: Es un RuleResult que siempre devuelve true.
* FalseResult: Es un RuleResult que siempre devuelve false.

Por último, OT-Rules tiene diferentes formas de ser configurado:
* XML: Es un formato XML que propone la framework. Proximamente tendrá un plugin para Eclipse.

<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>

* Spring: Como ya se explico arriba
* API: Se pueden crear las rules a mano o utilizar la FluentAPI que facilita la creación programatica de reglas

FluentRule fir = new FluentRule("test");
fir.rule(new TrueRule()).and(new TrueRule()).or(new FalseRule()).not();

RuleEvaluator evaluator = FluentInterfaceRuleEvaluatorBuilder.createRuleEvaluator(fir);

No hay comentarios:

Publicar un comentario