viernes, 9 de abril de 2010

JBoss Drools

Según la Wikipedia: "Las reglas de negocio describen las políticas, normas, operaciones, definiciones y restricciones presentes en una organización y que son de vital importancia para alcanzar sus objetivos. Las reglas de negocio especifican en detalle lo que una organización puede hacer."

Ejemplos de reglas de negocio podrían ser:
  • "A una persona que ingresa más de 20.000 euros al año Hacienda le retiene el 15%"
  • "A los clientes que gasten más de 1.000 euros se les hace un descuento del 5%"

Como se ve en los ejemplos, las reglas de negocio tienen una semántica if/then, si se cumple una determinada condición se debe realizar una determinada acción.

Cuando un negocio dispone de muchas reglas que se deben aplicar, las aplicaciones que las gestionan se convierten en una maraña de código formado por estructuras if/else anidadas, que dificultan enormemente la comprensión y mantenibilidad del código.

Si las reglas de negocio se aíslan del resto del código, se está separando la verdadera inteligencia sobre el negocio (las reglas) del resto del programa. Los sistemas de gestión de reglas de negocio (BRMS – Bussiness Rule Management System) permiten gestionar de forma independiente las reglas de negocio. Facilitan la modificación o inclusión de nuevas reglas en tiempo de ejecución.

Dentro de los sistemas de gestión de reglas de negocio en Java más populares hoy en día encontramos Drools. Drools es un BRMS de JBoss que implementa la Java Rule Engine API (JSR 94) y utiliza una implementación mejorada del algoritmo de Rete para la ejecución de reglas.

A continuación voy a mostrar un ejemplo simple de cómo utilizar Drools y cómo permite parametrizar las reglas del negocio de forma externa a la aplicación que las utiliza. Antes de comenzar, recomiendo haber instalado JBoss Tools en Eclipse, puesto que nos ofrecerá herramientas que facilitan el desarrollo de apicaciones que usan Drools.

El caso de uso que vamos a implementar en el ejemplo será el de una tienda online. En esta tienda hay dos tipos de clientes, aquellos que se han registrado previamente y los que no. Para todos los clientes que gasten más de 1.000 euros se les hace un descuento del 5%. A los que se han registrado, se les ofrece un descuento adicional del 5% en todas su compras, independientemente del importe. Vamos a hacer una aplicación que calcule el descuento que corresponde a cada usuario.

En primer lugar creamos un proyecto Drools en Eclipse, lo que es posible gracias a las utilidades instaladas en JBoss Tools:

El nuevo proyecto se va a llamar CalculadorDescuento:

El asistente nos permite crear ejemplos de uso de Drools:

Es necesario especificar el runtime de Drools, es decir, la carpeta donde se encuentran las librerías de Drools:


Ahora, creamos una clase Cliente que refleje los datos del cliente, su nombre, si está registrado, el gasto que ha realizado y el descuento que se le aplica:
package com.roldan.drools;

public class Cliente {
String nombre;
boolean registrado;
float gasto;
float descuento;

// Getters y setters...
}

En la clase CalculadorDescuento se crea el siguiente método, que lee las reglas que se han definido en el fichero Descuentos.drl:
private static KnowledgeBase readKnowledgeBase()
throws Exception {
KnowledgeBuilder kbuilder =
KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(
ResourceFactory.newClassPathResource("Descuentos.drl"),
ResourceType.DRL);
KnowledgeBuilderErrors errors = kbuilder.getErrors();
if (errors.size() > 0) {
for (KnowledgeBuilderError error: errors) {
System.err.println(error);
}
throw new IllegalArgumentException("Could not parse knowledge.");
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
return kbase;
}

El método main de esta clase crea una sesión de Drools y crea también varios clientes que introduce en esta sesión a los que se aplican las reglas que se definen en el fichero Descuentos.drl:
public static void main(String[] args) {
try {
// Cargar las reglas
KnowledgeBase kbase = readKnowledgeBase();
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
KnowledgeRuntimeLogger logger =
KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test");

// Cliente no registrado que gasta más de 1.000 euros
Cliente cliente1 = new Cliente();
cliente1.setNombre("Cliente 1");
cliente1.setRegistrado(false);
cliente1.setGasto(1200);
ksession.insert(cliente1);

// Cliente registrado que gasta menos de 1.000 euros
Cliente cliente2 = new Cliente();
cliente2.setNombre("Cliente 2");
cliente2.setRegistrado(true);
cliente2.setGasto(800);
ksession.insert(cliente2);

// Cliente registrado que gasta más de 1.000 euros
Cliente cliente3 = new Cliente();
cliente3.setNombre("Cliente 3");
cliente3.setRegistrado(true);
cliente3.setGasto(1600);
ksession.insert(cliente3);

ksession.fireAllRules();
logger.close();

System.out.println("El cliente 1 tiene un descuento de "
+ cliente1.getDescuento() + " euros.");
System.out.println("El cliente 2 tiene un descuento de "
+ cliente2.getDescuento() + " euros.");
System.out.println("El cliente 3 tiene un descuento de "
+ cliente3.getDescuento() + " euros.");

} catch (Throwable t) {
t.printStackTrace();
}
}

Ahora vamos a ver cómo se definen las reglas anteriormente descritas en el fichero Descuentos.drl:
package com.roldan.drools

rule "Gastos superior a 1.000 euros"
when
cliente : Cliente( gasto > 1000 )
then
System.out.println("El cliente "
+ cliente.getNombre() + " ha gastado más de 1.000 euros.");
cliente.setDescuento(
cliente.getDescuento() + cliente.getGasto()*5/100);
end

rule "Cliente registrado"
when
cliente : Cliente( registrado == true )
then
System.out.println("El cliente "
+ cliente.getNombre() + " está registrado.");
cliente.setDescuento(
cliente.getDescuento() + cliente.getGasto()*5/100);
end

Al ejecutar la clase principal, obtenemos la siguiente salida que nos informa sobre lo que ha pasado:
El cliente Cliente 3 está registrado.
El cliente Cliente 3 ha gastado más de 1.000 euros.
El cliente Cliente 2 está registrado.
El cliente Cliente 1 ha gastado más de 1.000 euros.

El cliente 1 tiene un descuento de 60.0 euros.
El cliente 2 tiene un descuento de 40.0 euros.
El cliente 3 tiene un descuento de 160.0 euros.

En esta traza vemos qué condiciones ha cumplido cada uno de los clientes y el resultado final de aplicarles todos los descuentos a los que tienen derecho según su condición.

Aunque este es un ejemplo muy sencillo, se puede apreciar que ahora, si se quisiese variar el descuento aplicado para cada uno de estos casos o aplicar un nuevo descuento según otra condición distinta, no haría falta manipular el código de la aplicación. Bastaría con modificar las reglas definidas de forma separada en el fichero Descuentos.drl.

Esto no es más que una sencilla aplicación de lo que ofrece Drools, aunque este BRMS es mucho más potente que esto y ofrece mucha más funcionalidad que ya iremos viendo.

Referencias:
Does Your Project Need a Rule Engine
Primeros pasos con Drools
Drools en Wikipedia
Bussiness rule en Wikipedia
Implement business logic with the Drools rules engine
Open Source BRE/BRMS JSR-94 compliant
Getting Started With the Java Rule Engine API (JSR 94): Toward Rule-Based Applications

7 comentarios:

  1. No sabia nada de Drools y gracias a tu post aprendi algo nuevo en el día de hoy, gracias por el tiempo que dedicaste a los demas , que tengas un buen día!

    ResponderEliminar
  2. Esto es exactamente lo que estaba buscando! Te felicito por este muy buen trabajo.

    ResponderEliminar
  3. Muchas gracias por la explicación... clara, concisa y útil.
    Tienes tutoriales como este mas avanzados? me refiero, donde Drools utilice inferencias

    ResponderEliminar
  4. Lo siento Paula, de momento éste es el único tutorial que he escrito sobre Drools.

    ResponderEliminar
  5. Muchas gracias, es un punto de entrada perfecto a Drools y al concepto de las reglas.

    ResponderEliminar
  6. Muchas gracias, es un gran acercamiento a Drools

    ResponderEliminar
  7. Hola,
    Estoy haciendo un proyecto con Drools y Liferay, y tengo un problema a la hora de crear reglas con 'OR'.
    Tengo una clase 'user' y una clase 'address', y cada usuario tiene una o más direcciones.
    En el 'Initialize rule' obtengo todas las direcciones del usuario y hago un insertLogical:
    ...
    for (Address userAddress : userAddresses) {
    insertLogical(userAddress);
    }

    Y luego creo una condición de este estilo:
    rule "Prueba"
    when
    mailusuario: User(emailAddress contains "@gmail.com") OR
    userAddress: Address(country.name in ("France", "Germany", "Spain"));
    then
    #usuario correcto
    retract(userAddress);
    end

    Pero da el siguiente error:
    Caused by: com.liferay.portal.kernel.bi.rules.RulesEngineException: [109,3]: [ERR 102] Line 109:3 mismatched input 'userAddress' in rule "Prueba"
    [0,0]: Parser returned a null Package


    Estoy utilizando la versión 5.5.0 de Drools.

    Muchas gracias.

    ResponderEliminar