No hay duda de que la parte principal y corazón de cualquier aplicación es el bloque de acciones de negocio que realiza, lo que comúnmente llamamos su core.

Con la premisa de proporcionar un ecosistema completo que permita definir, mantener, encapsular y ejecutar como una entidad única e independiente todas estas reglas de negocio nacen los Business Rule Management System (BRMS).

Hoy en el blog veremos cómo encaja Drools en todo este entramado, otros elementos de la suite de Red Hat BRMS y jugaremos un poco con la definición de reglas, para degustar la sencillez y potencia de esta herramienta.

Aplicaciones como herramientas y no como soluciones finales

En aplicaciones tradicionales cambiar algún flujo de negocio generalmente implica un evolutivo que conlleva un cambio de código fuente, planificación, redespliegue, etc., esto acaba transformándose en un proceso tedioso, tardío y poco flexible.

La historia suele suceder de la siguiente manera: un cliente nos pide construir una aplicación para dar solución a un problema o necesidad y siempre viene acompañada de unos requisitos de negocio (que se haga accesible la web, que mande un correo cuando te registres, que guarde archivos, que haga tal o cual cosa). El cliente tiene claras 3 o 4 funcionalidades y con nuestra ayuda termina de definir la aplicación.

Reflexionando sobre esta historia, ¿no es mejor orientar la construcción de la solución como una herramienta con requisitos, pero que su comportamiento core sea editable?

Construye una única vez la aplicación con sus requisitos básicos: accesible por la web, escalable, con integraciones para mandar correos, guardar archivos, específicas que den solución a tal o cual funcionalidad y ofrezcamos un fácil acceso al corazón del negocio para poder modificarlo y ajustarlo de manera sencilla si fuera necesario.

Consideraciones antes de entrar en materia

Cuando desarrollamos aplicaciones, habitualmente nuestro código describe “a mano” qué funciones y en qué orden se deben invocar para cumplir una determinada funcionalidad.

El concepto de un motor de reglas, “es un poco al revés”. Las aplicaciones están compuestas por entidades. Estas entidades tienen distintos estados y estos pueden variar.

Los estados de estas entidades harán que el motor de reglas se comporte de una manera u otra, desencadenando acciones sobre esas entidades o cualquier otro tipo de funcionalidad.

El motor de reglas se ejecuta como una "caja negra" portable dentro de nuestra aplicación, de forma que el desacoplado de capas está garantizado.

Business Rule Management System (BRMS)

El uso de un BRMS nos proporciona muchas ventajas. Estas son algunas de ellas:

¡Vamos allá!

En este tutorial vamos a ver explorar Drools, que es la implementación de Red Hat para un modelo BRMS, pero existen otros productos en el mercado como:

DROOLS (JBoss Rules)

Drools es un Business Rule Engine implementado por Red Hat que provee de herramientas para la definición de las reglas. También se ocupa de su proceso de compilación, recreación del árbol de flujos/decisiones (basado en el algoritmo de inferencia Rete).

Rete (Dr.Charles L. Forgy) es un algoritmo de reconocimiento de patrones, que en base a las reglas definidas y posibles acciones derivadas (inferidas) se genera un árbol de decisiones. Rete es hoy en día la base de muchos famosos sistemas expertos (CLIPS, Jess, Drools, Soar...).

Su enfoque se basa en crear una estructura completa de nodos de navegacion basados en un conjunto de reglas (la base de conocimiento). Los nodos son relacionados en base a las consecuencias que se podrían inferir de los estados de los objetos que intervengan en la ejecución y vaya circulando por los nodos.

Este enfoque, aunque define más flujos posibles y, por lo tanto, hace mayor uso de memoria, otorga una ejecución más rápida del motor en comparación con un enfoque “tradicional” en el cual se comprobarán todas las reglas cada vez para saber si se tienen que ejecutar o no.

Drools se caracteriza por ofrecernos una forma sencilla de declarar las reglas, ya sea a través de ficheros .drl (drools rule language) o directamente a través de ficheros excel.

Esta capacidad de definición a través de ficheros excel es muy valorable desde el punto de vista de negocio, ya que perfiles no técnicos pueden definir cómo se debe comportar la aplicación.

KIE (DROOLS6 + OPTAPLANNER + JBPM)

KIE Engine (knowledge is everything) también pertenece a la suite de Red Hat y engloba todas las herramientas para la ejecución del motor de reglas. En la ejecución de un motor de reglas encontramos 2 fases fundamentales:

  1. Generación del árbol de decisiones, cuyo encargado es el Business Rule Engine (Drools).
  2. El proceso de toma de decisiones al navegar por el árbol en tiempo de ejecución, implementada por el framework OptaPlanner (Java).

Todo el proceso es gobernado y manejado por el motor JBPM, que es el encargado de ejecutar flujos de trabajo y reglas. Esta en Java y también es un producto de Red Hat .

Este motor BPM nos permite suscribirnos a los eventos y acciones que suceden dentro del él y nos proporcionan de un conjunto bastante amplio de APIs para manejarlo.

KIE Execution Server (Wildfly)

Aunque el propósito de este tutorial es ejecutar el motor de reglas dentro de una aplicación propia construida sobre Spring Boot, la suite de Red Hat BRMS tiene otro producto a modo servidor centralizado de motor de reglas.

KIE Execution Server es una aplicación distribuida en formato .war, clusterizable que se despliega sobre el servidor J2EE de la casa WildFly.

Esta aplicación expone un motor de reglas accesible a través de un API Rest donde nuestro distintos servicios pueden hacer sus invocaciones. Puedes encontrar más información aquí.

Business Central WorkBench (Drools workbench)

Otro de los tentáculos de la suite de Red Hat para los sistemas BRMS es el proyecto Business Central Workbench. Es una herramienta que nos permite, a través de un servidor web, manejar de manera centralizada y colaborativa en la definición de las reglas, editarlas, versionarlas y servir configuración compartida a distintos servidores de ejecución.

Aunando las herramientas actuales, podemos ver una foto general de cómo se puede manejar una infraestructura compleja, donde tenemos un WorkBench para unificar, definir nuestras reglas, y proporcionar de repositorio común a todos los KIE Execution Servers que den soporte a distintas áreas de nuestro negocio. La imagen es bastante ilustrativa:

Definiciones

En el ecosistema de un BRMS, encontramos varios conceptos y agentes que intervienen en su configuración y ejecución.

La forma en la cual se buscan, indexan e interpretan las definiciones de reglas, el estado de contexto a usar (stateless/stateful) y, en definitiva, la forma en la que el BPM compila y actúa es totalmente editable desde el API que nos proporciona jBPM y Drools.

Preparación del entorno

En este tutorial vamos a integrar la ejecución del motor de reglas sobre un proyecto de Spring Boot. Para ello nos basaremos en las libs de drools core para la definición de las reglas y el framework KIE para la ejecución de las mismas.

Podemos importar todas las dependencias desde los repositorios centrales de maven:

<!-- JBPM and Spring integration -->
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>7.18.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-spring</artifactId>
    <version>7.18.0.Final</version>
</dependency>

Preparando la ejecución para unos test aplicativos

Para que nos vayamos introduciendo en el manejo de Drools, vamos a hacer unos ejemplos muy sencillos donde aprenderemos a definir con unas reglas sencillas.

Este primer ejemplo es muy simple, al contexto de ejecución de Drools le vamos introducir un Fact que define el precio de un producto, este será analizado por las reglas y se dispararán una serie de acciones.

Lo primero generar la configuración

Para instanciar el motor del engine mediante Spring Boot basta con generar nuestra clase de configuración usando el archiconocido @Configuration y un método que inyecte en el contenedor un Bean de tipo KIEContainer.

Este KIEContainer encapsula todos los elementos de los que hablamos al principio (KnowledgeBase, configuración Working Memory, etc.) y nos proporcionará un API sencillo para interactuar con todos ellos.

En este caso lo usaremos para crear la KnowledgeSession cada vez que requiramos una ejecución.

Cada ejecución tiene su contexto, por lo tanto su runtime, que es configurable en modo stateless o stateful:

@Configuration
public class BPMConfigurations {
 //lives on classpath -- src/main/resources/rules/*.drl 
 private static final String[] drlFiles = { "rules/discountRules.drl" };
 @Bean
 public KieContainer kieContainer() {
 KieServices kieServices = KieServices.Factory.get(); 
 //Load Rules and Ecosystem Definitions
 KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
 for (String ruleFile : drlFiles) {
 kieFileSystem.write(ResourceFactory.newClassPathResource(ruleFile));
 }
 //Generate Modules and all internal Structures
 KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
 kieBuilder.buildAll();
 KieModule kieModule = kieBuilder.getKieModule();
 return kieServices.newKieContainer(kieModule.getReleaseId());
 }
}

Como podemos ver, KIE nos abstrae de la configuración de los Modules BPM (y demás configuración subyacente).

Con este Bean en el contexto ya podemos empezar a trastear. Le especificamos que cargue un fichero de reglas que se encuentran en el classpath: “src/main/resources/rules/discountRules.drl” (el KIEContainer hace todo por nosotros).

Ahora generamos un POJO para actuar como Fact (ProductPrice.java) muy sencillo (he usado lombok framework para excluir código genérico “innecesario”):

@Getter 
@Setter 
@NoArgsConstructor 
@ToString
public class ProductPrice {
 private Integer basePrice;
 public ProductPrice(Integer basePrice) {
 this.basePrice=basePrice;
 }
}

Ahora una clase de Servicio muy sencilla, que nos provea del acceso a la ejecución:

@Service
public class PriceCalculatorService {
 @Autowired
    private KieContainer kieContainer;
 public void executeRules(ProductPrice productPrice) {
     KieSession kieSession = kieContainer.newKieSession();
        kieSession.insert(productPrice);
        kieSession.fireAllRules();
        kieSession.dispose();
    }

Y, por último, un punto de entrada para poder ejecutar nuestro código. Es este caso un JUnit test:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DroolsDemoApplicationTests {
 @Autowired
 private PriceCalculatorService priceCalculatorService;
 @Test
 public void executeCalculations() {
 ProductPrice productPrice = new ProductPrice(5);//Create the Fact
 priceCalculatorService.executeRules(productPrice);//Call service and internal 
    //BlackBox rules engine
 System.out.println(productPrice);//final object state after rules execution
 }
}

Vemos que:

Esta configuración y el código puesto son más que suficientes para hacer nuestras pruebas y juguetear un poco.

Vamos a ver el contenido de nuestra definición de reglas del archivo discountRules.drl.

Esta primera versión es muy sencilla, al introducir el producto en el engine chequeará si el precio del producto es mayor que 2, y si es así, hará un print por consola.

discountRules.drl

package myAppRules;

import com.dppware.droolsDemo.bean.*;

dialect  "mvel"

rule "Adjust Product Price"
    when
     $p : ProductPrice(basePrice > 2 )
    then
     System.out.println("EJECUTANDO -Adjust Product Price- para el producto [" + $p + "]");
end

Podemos intuir que importa unos tipos de objeto y que hay una rule que tiene una condición y que si se cumple hace un system Out. Por partes:

$person.getAddresses().get("home").setStreetName("my street");

$person.addresses["home"].streetName = "my street";

Ejecución

Si ejecutamos el test, estos son los pasos fundamentales:

ProductPrice productPrice = new ProductPrice(5);//Create the Fact
priceCalculatorService.executeRules(productPrice);//Call service and internal 
                                                   // BlackBox rules engine

$p : ProductPrice(basePrice > 2 )  // Object({conditions}) 

Las condiciones disponibles (Fact({conditions}) las vamos a ver más adelante.

System.out.println("EJECUTANDO -Adjust Product Price- para el producto [" + $p + "]");

Conditions (revisando condiciones en LHS - Left Hand Side)

Como hemos visto en el paso 2 anterior, se cumple la condición de que basePrice > 2 (ya que era 5 cuando entró al contexto de ejecución).

Para el ejemplo anterior pondríamos condiciones más complejas:

( > , < , >=, =<, || , && , == , % , ^, contains, not contains, memberof, not memberof, matches (regExp), not matches (regExp), starswith , etc...)

Ejemplos:

$p : ProductPrice(((basePrice / 5) == 1) && ((basePrice % 5) == 0 ))

En vez de anidarlos también podríamos usar la cláusula por defecto and y convertir la condición anterior en:

when
 $p : ProductPrice((basePrice / 5) == 1))
 $p : ProductPrice((basePrice % 5) == 0 ) //se deben cumplir todas
 then

Existe una evaluación “en modo manual” con la palabra reservada “eval” que nos permite condicionar la ejecución en base a un valor booleano (eval({true|false}).

when
 eval(true)
 eval ($p.isZeroPrice()) //por ejemplo link a un método booleano interno de la clase
 eval(callMyCustomFunctionThatReturnsABoolean)
 then

En el segundo eval, dejo ver que Drools, como compilador, nos permite referenciar a métodos estáticos de nuestro código o definir funciones dentro del mismo fichero .drl que como ya vimos estarán disponibles en todo el package (namespace).

Modify{...: aplicando órdenes en el then RHS

Ya hemos visto las conditions*,* pero vamos a ver las órdenes. Hemos visto una sencilla ejecución con el system out, referenciando a la variable local de la rule $p:

then
     System.out.println("EJECUTANDO -Adjust Product Price- para el producto [" + $p + "]");

Si queremos aplicar una modificación, vamos a usar el bloque modify. Vamos a bajarle el basePrice un punto y nos quedará así:

import com.dppware.droolsDemo.bean.*;
dialect  "mvel"
rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice - 5);
     }
     System.out.println("el precio ajustado es " + $p.basePrice);
end

Sencillo, ¿verdad? Abrimos un bloque modify con el scope de la variable definida dentro de la función (ya que se cumplió el LHS- Left Hand Side).

En el contexto encontramos la función setBasePrice del objeto ProductPrice. Si modificamos el código y ejecutamos el Test, veremos este output: el precio ajustado es 0.

Rules Attributes

El comportamiento en runtime de una rule se puede restringir. Para ello, debemos usar los rule attributes que nos ofrece drools. Estos atributos se definen justo debajo al empezar la rule:

rule 'rulename'
 //rules Atributtes (availables: no-loop, salience, ruleflow-group, lock-on-active, agenda-group, 
 // activation-group, auto-focus, dialect, date-effective, date-expires, duratio, timer, calendars
 when:
 ...
 then:
 ...
end

Vamos a comentar dos de ellas (las que me parecen más genéricas y obligatorias de conocer), si quieres profundizar tienes la referencia oficial aquí.

No-loop

Imaginemos que metemos un Fact (ProductPrice.java) que su basePrice = 5. Cuando se cumple la condición solo le restamos 1 a su basePrice.

rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
 then 
     modify($p){
      setBasePrice($p.basePrice -1);
     }
     System.out.println("el precio ajustado es " + $p.basePrice);
end

Drools, por defecto, volverá a evaluar la LHS y verá que sigue cumpliéndose así que la regla será disparada varias veces, hasta que no se cumpla la condición.

Y el Ouput:

Esto puede ser un quebradero de cabeza en la ejecución, porque si la ejecución es de tipo void y no se modifica el Fact, podríamos caer en un bucle infinito.

Para asegurar que la regla (si se cumple) solo se ejecute una vez, usamos el atributo no-loop:

rule "Adjust Product Price"
 no-loop
    when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice -1);
     }
     System.out.println("el precio ajustado es " + $p.basePrice);
end

Y ahora el Output:

Salience

Por defecto, Drools ordena las reglas de ejecución en el orden que se las va encontrando al parsear los ficheros de reglas (.drl). De modo que en tiempo de ejecución se ejecutarán las reglas que su RHS cumpla y por el orden en el que se almacenaron en el motor.

Imagina esta definición de drl en la que hemos introducido dos reglas (ya añadimos el no-loop también):

rule "Adjust Product Price"
 no-loop
    when
     $p : ProductPrice(basePrice > 2 )
    then
     System.out.println("EJECUTANDO -Adjust Product Price-");
end
rule "Sending Notification"
 no-loop
    when
     $p : ProductPrice(basePrice > 2 )
    then
     System.out.println("EJECUTANDO -Sending Notification-");
end

Y su Output:

Salience (prominencia) es un sistema de pesos que nos permite indicar prioridades sobre las ejecuciones de las reglas en caso de coincidencia. Si añadimos el atributo salience a las reglas vemos como podemos especificar el orden:

rule "Adjust Product Price"
 no-loop
 salience 1
    when
     $p : ProductPrice(basePrice > 2 )
    then
     System.out.println("EJECUTANDO -Adjust Product Price-");
end
rule "Sending Notification"
 no-loop
 salience 2
    when
     $p : ProductPrice(basePrice > 2 )
    then
     System.out.println("EJECUTANDO -Sending Notification-");
end

Y su Output:

Ahora se han ejecutado ordenadamente en función del peso (valor) de nuestra rule dentro del engine. Los valores de salience pueden ser negativos si queremos que nuestra regla tenga prioridad mínima y ser lanzada en última instancia.

Agenda -Groups/Focus

El concepto de Agenda, dentro de la ejecución del motor de reglas, hace referencia a todas las reglas que se detectan en el ecosistema y que están a la espera de ser ejecutadas (si se satisface su LHS).

La meta-informacion de “ageda-group” nos permite taggear o clasificar las reglas de forma que podemos en runtime excluir o incluir solo aquellas que queremos que se procesen.

/**
* Prints hello every execution
**/
rule "Welcome Message"
    agenda-group "MessagesGroup"
    when
 $m : Message()
 then
   System.out.println("Hello Mr.");
   m.setMessage("My message");
end

Y, para ejecutarla, debemos acceder a la session que contiene esta base de conocimiento (es decir, esta regla). Después, acceder a las agendas y poner el foco solo sobre ella. Así, en el proceso de ejecución solo se ejecutarán las reglas pertenecientes a esa agenda.

knoledgeSession.getAgenda().getAgendaGroup(“MessagesGroup”).setFocus();
knoledgeSession.fireAllRules();

En este caso, solo se modifica la sesión de ejecución de Drools para que solo ejecute las reglas que pertenezcan al agenda-group “Group One”.

Date-Efective, Date-Expires

En este caso, podemos especificar clausulas de tiempo en la LHS, que condiciona si una regla se va a ejecutar o no.

Drools expecifica por defecto el dateFormat (dd-mmm-yyyy -> “01-SEP-2012”), pero podemos cambiarlo seteando:

System.setProperty(“drools.dateformat”, “yyyy/MM/dd”);

Esta regla solo se aplicaría para las ejecuciones del día especificado.

/**
* anniversary custom message
**/
rule "2 of June show amazing Messages"
    date-effective "2019/06/04"
    date-expires "2019/06/05"
    when
 $t : Task()
 then
   $t.setDescription(($t.getDescription()+ " .This task has beeen created in our company 25th anniversary");
   modify($t)
end

Calendars

El motor para acciones temporales de Drools está delegado en Quartz, por lo que podemos definir calendarios y reglas de cronología de ejecución:

rule "scheduled Rule"
calendars "only-weekdays"
when
    $task: Task()
then
    System.out.println("Is weekday");
end

Y para la ejecución en Java, es necesario inyectar el calendar en la session.

StatefulKnowledgeSession ksession = createknowledgeSession();
WeeklyCalendar calendar = new WeeklyCalendar();  // org.quartz.impl.calendar.WeeklyCalendar
org.drools.time.Calendar onlyWeekDays = QuartzHelper.quartzCalendarAdapter(calendar);
ksession.getCalendars().set("only-weekdays", onlyWeekDays);
ksession.insert(new Task());
ksession.fireAllRules();

Timers

Se pueden ejecutar condiciones temporales de manera sencilla mediante el uso de Timers:

rule "Siren_2"
    timer (int: 2s 2s)  //empieza a los 2 segundos y se repite cada 2 segundos)
    when
   $alarm : CentralAlarm(status == "fired")
   $siren: Siren()
    then
   $siren.trigger();
end

Para hacer uso de las reglas de timer es necesario ejecutar las reglas en modo “fireuntilHalt”:

new Thread(){
 @Override
 public void run(){
 kieSession.fireUntilHalt();
}
}.start();

Usando el compilador

Podemos importar funciones, crear nuevos tipos, funciones dentro del drl, etc…

functions - > Importando métodos de utilidades:

Pongamos que tenemos una librería de utilidades, parseos o que realiza cualquier otro tipo de funcionalidad atómica y que nos vendría fenomenal usarlo dentro de nuestras reglas.

Desde Drools podemos importar ese método para usarlo dentro de las rules (hay que tener en cuenta que será accesible dentro de todo su namespace/package).

Pues es tan fácil como definir el método con acceso static en nuestra clase java y luego importarlo en nuestro fichero .drl:

La clase Java:

public class Utils {
 public static void prettyTraces(Object message) {
 System.out.println("PrettyTraces -> ***"+message+"***");
 }
}

Para usarla en nuestra rule basta con importarlo de manera declarativa completa.

import com.dppware.droolsDemo.bean.*;

//Imported specified functions
import function com.dppware.droolsDemo.utils.Utils.prettyTraces;

dialect  "mvel"

rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice - 5);
     }
     prettyTraces("el precio ajustado es " + $p.basePrice);
end

Output: PrettyTraces -> ***el precio ajustado es 0***

También puedes observar que he metido // para los comentarios.

functions - > Creandolas en drl:

Definir funciones dentro del .drl es muy sencillo, usamos la palabra reservada function. Vamos a incrementar el código metiendo la definición también:

import com.dppware.droolsDemo.bean.*;

//Imported specified functions
import function com.dppware.droolsDemo.utils.Utils.prettyTraces;

dialect  "mvel"

//functions inline definition
function Integer calculateIncrement(Integer value, int quantity) {
    return value + quantity;
}



rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice - 5);
     }
     prettyTraces("el precio ajustado es " + $p.basePrice);
     prettyTraces(calculateIncrement($p.basePrice, 3));
end

Output:

Vemos cómo hacemos la llamada al método que acabamos de crear y además al prettyTraces que habíamos añadido anteriormente.

Nuevos Tipos (TYPE - class)

Es posible definir nuestras nuevas clases dentro del ecosistema de manera declarativa y sin compilacion previa, ya que el compilador leerá la definición e introducirá en el classloader las definiciones para la posible instanciación en runtime.

La declaración de nuevos tipos se hace en la misma sección de declaración de las funciones, usando la palabra reservada "declare".

Por defecto, con el uso de "mvel", los getters/setters/toString/equals/hashCode serán añadidos a la definición.

El compilador creará dos constructores por defecto (uno vacío y otro con todos los campos como argumentos).Si queremos customizar el constructor para usar solo determinados argumentos, debemos usar la anotación @key. Puedes ver aquí más ejemplos en la documentación oficial.

declare Product
   code : int
   name : String
   description : String
ends

Y para su instanciación se puede hacer de esta manera usando sintaxis java (recuerda que esta sintaxis solo es aceptada en RHS):

import com.dppware.droolsDemo.bean.*;

//Imported specified functions
import function com.dppware.droolsDemo.utils.Utils.prettyTraces;

dialect  "mvel"

//functions inline definition
function Integer calculateIncrement(Integer value, int quantity) {
    return value + quantity;
}

//New Types definition
declare Product
   code : int
   name : String
   description : String
end

rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice - 5);
     }
     prettyTraces("el precio ajustado es " + $p.basePrice);
     prettyTraces(calculateIncrement($p.basePrice, 3));
     //Instanciacion y print del object
     Product pro = new Product();
     pro.setCode(3321);
     pro.setName("Leche");
     pro.setDescription("Rica en Calcio");
     prettyTraces(pro);
     
end

Y el Output al ejecutarlo es:

Como podemos observar, nos está quedando el código bastante feo en este archivo .drl, ya que se nos empieza a ir de las manos.

Empieza a tener sentido el concepto de "package". Organicemos ficheros .drl (uno con definiciones de tipos, otro con funciones y otro con las rules). Todos deben definir el mismo package (namespace) en su cabecera y así seguirán disponibles de manera "global" dentro del mismo namespace.

En la siguiente sección veremos cómo incluir varios ficheros, pero antes vamos a ver los "Global" que va muy relacionado con lo que acabamos de ver.

Importar objects desde el contexto java (globals)

Hemos visto ya que podemos:

Y ahora vamos a ver cómo meter objetos ya instanciados y disponibles en la máquina virtual al contexto de ejecución de Drools.

Puede ser muy útil inyectar un bean de servicio de nuestro contexto de Spring para que sea utilizado en las LHS para realizar operaciones complejas (piensa en insertar un DAO o cualquier tipo de Servicio).

A este tipo de inyecciones se las denomina globals. Para estas sí que necesitamos editar nuestro test Java, ya que se lo pasamos como argumento en el proceso de construcción de la ejecución.

Definimos un @Service Java y le añadimos un método muy básico que simule que publica en un topic (es solo para que te hagas una idea):

@Service
public class PushSubService {
 //@Autowired
 //private KafkaTemplate<String, String> kafkaTemplate;
 public void publishNewProductCreated(Object o) throws JsonProcessingException {
 String rawJSON = new ObjectMapper().writeValueAsString(o);
 //kafkaTemplate.send("newProduct", rawJSON); ...or whatelse
 System.out.println("Publishing newProduct Topic , content ["+rawJSON+"]");
 }
}

Ahora nos interesa tener este objeto dentro del contexto de ejecución de las rules de drools, así que lo inyectamos como global cuando solicitamos la ejecución.

La clase PriceCalculatorService.java (la del principio, ¡recuerda!) llevamos un buen rato sin tocarla y, en mi opinión, esto es lo bueno de drools, que solo montamos los "hierros" en java y lo delegamos todo en el motor de reglas que debería actuar como una caja negra para nosotros.

Modificamos el método para añadir un "globals" al contexto de ejecución. Para ello usamos el API que nos proporciona la KieSession:

@Autowired
private PushSubService pushSubService;
public void executeRules(ProductPrice productPrice) {
     KieSession kieSession = kieContainer.newKieSession();
     kieSession.setGlobal("publishTool", pushSubService);//adding globals
     kieSession.insert(productPrice);
        kieSession.fireAllRules();
        kieSession.dispose();
}

He inyectado el servicio a través del global name "publishTool" y lo referencio dentro del .drl en la parte de definiciones que estábamos usando para las functions y demás tipos que vimos anteriormente.

global com.dppware.droolsDemo.services.PushSubService publishTool;

En la RHS (then) lo tenemos disponible:

publishTool.publishNewProductCreated(pro); 

El archivo .drl ahora tiene este contenido:

import com.dppware.droolsDemo.bean.*;

//Imported specified functions
import function com.dppware.droolsDemo.utils.Utils.prettyTraces;

//global Sets
global com.dppware.droolsDemo.services.PushSubService publishTool;

dialect  "mvel"

//functions inline definition
function Integer calculateIncrement(Integer value, int quantity) {
    return value + quantity;
}

//New Types definition
declare Product
   code : int
   name : String
   description : String
end

rule "Adjust Product Price"
 when
     $p : ProductPrice(basePrice > 2 )
    then
     modify($p){
      setBasePrice($p.basePrice - 5);
     }
     prettyTraces("el precio ajustado es " + $p.basePrice);
     prettyTraces(calculateIncrement($p.basePrice, 3));
     //Instanciacion y print del object
     Product pro = new Product();
     pro.setCode(3321);
     pro.setName("Leche");
     pro.setDescription("Rica en Calcio");
     prettyTraces(pro);
     publishTool.publishNewProductCreated(pro);
     
end

Output:

Conclusiones

Hasta aquí hemos visto que Drools es un motor de reglas y nos hemos empezado a utilizarlo un poquito. Intentar abarcar Drools en un tutorial es una osadía, te recomiendo seguir los links de la documentación oficial que he ido dejando a lo largo del documento.

En la segunda parte del tutorial vamos a ver cómo organizar el código y algunos apuntes más del API que proporciona KIE.

Referencias

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.