Data ScienceOpenShift

Really Simple Rules Service

The Drools rules engine provides a very powerful business rule processing capability.  However, implementing even the most simple of use cases may require considerable client side scaffolding code.  This post presents a pattern on how Drools services can be consumed and produced as a very simple service – really simple rules service (RSRS).  The method was used in the implementation of the Drools CEP package for R as described at CEP made EasyR A comprehensive use case example of this is described at Weight Watcher. RSRS is even easier on OpenShift, check out Decision Service Made Easy with OpenShift .

FIFO: Facts-in Facts-Out

The pattern is based on the notion of facts in and facts out (FIFO).  Both incoming facts and outgoing represented as JSON objects.  The MVEL rule definitions of the RSRS reasons over the incoming facts and the rule consequences are outgoing facts.  Additional fact parameters can be added to categorise or shard facts such as e.g. a programid to simulate multi-tenancy and/or a groupid as a convenience key to segment facts within programid.

RSRS

BQL: Behavioural Query Language

The RSRS reads and create facts so fulfilling the two components of a CRUD capability.  MVEL then becomes our Behavioural Query Language (BQL).  BQL is to facts as SQL is to tables.  The client application consumes the RSRS result set to implement application consequence.  In the referenced example RSRS APIs are expressed via R packaging but could just as easily be via, e.g. a REST protocol.

MVEL

For this pattern, facts are declared and populated within the MVEL itself.  Rules file MVEL syntax follows pattern of three basic sections processed in order according to the assigned rule salience.  Follow the commentary in the sample code below for details.  Note that this could be easily split into two rules using salience to ensure priority for the Fact population section, as per the weightwatcher example, but is consolidated here for simplicity.  Later revisions will also look at generalising the payload using the JSON:API model.  To summarise:

  1. Variable declaration section to define fact types for the domain model
  2. Fact rules section to insert new facts based on incoming type
  3. Business rules section where rules are processed against facts, consequence is output fact
// 1. Variable declaration section

import java.util.HashMap;
import org.json.JSONObject;
import java.util.Date;
import java.text.SimpleDateFormat;
import com.satimetry.nudge.Output;

// The output variable contains the JSON result set.
// The client application can inspect the contents of output
// using e.g. Drools APIs.
global java.util.HashMap output;
global SimpleDateFormat inSDF;
global SimpleDateFormat outSDF;

function void print(String txt) {
System.out.println(txt);
}

// Facts are declared within the MVEL.
// This example sets up a CEP time-series of facts.
declare Observation
@role( event )
@timestamp( obsdate )
obsid : String @key
obsdate: Date @key
obsvalue: Integer
end

// 2. Incoming fact create section
// A rule with high salience is used to populate the incoming facts.
// Note the entry-point by which incoming JSON objects are streamed to the
// Drools rules engine.
rule "ruleInsertObservation"
  salience 1000
when
  $input : JSONObject() from entry-point DEFAULT
then
  inSDF = new SimpleDateFormat("yyyy-M-d h:m:s");
  Date obsdate = inSDF.parse( $input.get("obsdate").toString() );
  Observation $observation = new Observation( $input.get("obsid").toString(), obsdate );
  $observation.setObsvalue( Integer.parseInt($input.get("obsvalue").toString()) );
  insert( $observation );
  print(drools.getRule().getName() + "->" + $observation.getObsid() + "-" + $observation.getObsdate() );
end

// 3. Business Rules
// This is the actual business rules with low salience to ensure they are triggered
// after the incoming facts are created.
// This example shows an aggregate function applied to a time-series of values.
rule "ruleTotalValue"
  salience -1000
  no-loop true
when
  $total : Number( intValue > 0) from accumulate(
  Observation( $obsvalue: obsvalue ) over window:time( 30d ),
  sum ( $obsvalue ) )
then
  JSONObject joutput = new JSONObject();
  joutput.put("rulename", drools.getRule().getName());
  joutput.put("rulevalue", $total);
  Output $output = new Output(joutput.toString());
  insert($output);
  print(drools.getRule().getName() + "->" + $total);
end

Leave a Reply