-->
Showing posts with label boo. Show all posts
Showing posts with label boo. Show all posts

Friday, 28 November 2008

How to use NGinn rules engine

1. Required libraries

To use NGinn.RippleBoo engine in your application you need to add references to the following libraries:

  • NGinn.RippleBoo

  • Rhino.DSL.dll

  • Boo.Lang.dll

  • Boo.Lang.Compiler.dll

  • NLog.dll



2. Invoking RippleBoo

The code below shows how to configure rule repository and how to execute some rules.

using System;
using System.Collections.Generic;
using NGinn.RippleBoo;

class TestMe
{
private RuleRepository _repos;

public TestMe()
{
_repos = new RuleRepository();
_repos.BaseDirectory = "c:\\rules";
_repos.ImportNamespaces.Add("System");
}

public void RunSomeRules()
{
Dictionary<string, object> variables = new Dictionary<string,object>();
variables["Email"] = "my@email.com";
variables["Timestamp"] = DateTime.Now;

Dictionary<string, object> context = new Dictionary<string,object>();
context["Output"] = Console.Output;

_repos.EvaluateRules("some_rules.boo", variables, context);
}
}


RuleRepository class stores common configuration properties for your rules and allows you to call rules stored as '*.boo' files in base directory. It compiles the rule scripts and caches them so subsequent evaluations are fast. If rule script changes, it will be automatically recompiled. You should create rule repository once and hold it as long as needed.

Rule evaluation is done in 'RunSomeRules' method. To execute rules you call RuleRepository.EvaluateRules, passing the rule file name and two dictionaries.
First one contains variables that can be referenced from rules through 'Variables' object. The second one contains 'context' object, they can be referenced from rules throug 'Context' object.

Example rule:
ruleset "SomeRules":
rule "R1":
when Variables.Email.EndsWith("mydomain.com")
action:
Context.Output.WriteLine("Email from my domain")



This rule references the 'Email' variable and the 'Output' context object. Please note that in rules you don't have to quote the variable names - it's because of Boo language's IQuackFu magic interface.
RuleRepository.EvaluateRules method is thread safe.

Thursday, 27 November 2008

Rules engine improved

First attempts to use the RippleBoo rules engine in real software showed that version 0.1 wasn't very useful, so I had to prepare version 0.2.
First of all, the structure of rule definition was changed - now it's more descriptive:



rule "SPAM":
label "Spam? - move to spam"
when IS_SPAM()
except_rule "Friendly_spam"
action:
MOVE_TO "Spam"
else_rule "WORK"



What we have here:

  • declaration of rule "SPAM"
  • label - for documentation
  • when - this is rule condition
  • except_rule - this is the 'exception' rule - containing an exception for the rule condition. Our 'SPAM' rule will be fired when its condition is true and the exception does not fire
  • action - executed when rule fires
  • else_rule - successor when rule condition is not satisfied


You should read it like so: when IS_SPAM() returs true, move message to 'SPAM' folder, except for messages where "Friendly spam" rule applies. If IS_SPAM() returns false, do nothing but proceed to rule "WORK"
That's basically how Ripple Down Rules work. You should note that only one rule will be fired - the one with satisfied condition and no exceptions to apply. This can be a problem when you want to execute some code each time rule condition is satisfied, no matter if there are exceptions or not. In such case you can either put your code in rule condition, or use special 'side_effect' block:



rule "VERY_IMPORTANT":
label "Important? - mark high priority"
when __msg.From == "customer_care@mybank.com"
side_effect:
__msg.Priority = "High"



The 'side_effect' will be executed just after rule condition evals to true but BEFORE checking exception rules. In contrast, the 'action' block will be executed only when rule conditions eval to true AND no exceptions apply (no rule is fired when evaling exception subtree).


Here's an example rule definition file, containing simple email message processing rules. NGinn.RippleBoo engine allows you to declare your own 'local' variables and helper functions that can be used in rules:

#variable alias
__msg = Variables.Message

#helper function - check if message is spam
IS_SPAM = def() :
return __msg.Subject.IndexOf("[--spam--]") >= 0

#helper - move message to specified folder
MOVE_TO = def(folder):
Context.MessageDb.MoveMessage(__msg, folder)


ruleset "Email_default_rules":

rule "SPAM":
label "Spam? - move to spam"
when IS_SPAM()
except_rule "Friendly_spam"
action:
MOVE_TO "Spam"
else_rule "WORK"

rule "Friendly_spam":
label "Interesting subject? - read!"
when __msg.Subject.IndexOf("enlarge") >= 0
action:
MOVE_TO "Useful_spam"

rule "WORK":
label "Work? - move to WORK"
when __msg.From.EndsWith("mycompany.com")
action:
MOVE_TO "Work"
else_rule "VERY_IMPORTANT"


rule "VERY_IMPORTANT":
label "Important? - mark high priority"
when __msg.From == "customer_care@mybank.com"
side_effect:
__msg.Priority = "High"




And here's a graphical representation of the ruleset defined above.



The picture is automatically generated from rule definition, using the GraphViz tool (useful, but very user-unfriendly, unix-style program).

Other features


What is important, we can define several rulesets in single file. First ruleset will be the default one, but RippleBoo allows you to call also the other rulesets.
You can also call other rulesets from your actions, by executing
goto_ruleset "another ruleset"

Think of secondary rulesets as sub-procedures that can be called from the main procedure.

There is also an option to execute rules from external file
goto_file "another_rules.boo"

This will execute rules from another file.
Remember, you call goto_ruleset or goto_file from an action block inside some rule. Only one action will be executed, so you don't need to worry about continuation after goto - because there will be no continuation. Simply - there is no return from goto_ruleset or goto_file.



OK, I'll shed some light on using RippleBoo in your programs in next posts, because now I'm getting sick of code formatting at this blog engine. Does anyone know why it sucks so much and what can I do so it stops messing with my html?

Friday, 24 October 2008

Rules engine for NGinn

Today I have added a first working version of a rules engine to NGinn. The source code is in 'NGinn.RippleBoo' folder. 

The RippleBoo engine implements algorighm called 'Ripple Down Rules' - basically it is a binary decision tree. Each rule has simple "if then " structure, where condition is a boolean expression and action is a block of instructions. Apart from that, rule defines what will be the next rule to evaluate by specifying successor rule in positive case and successor rule in negative case. 

When rule condition evals to true, its action is executed and next rule to evaluate will be the 'positive' successor rule. When condition evals to false, action will not be executed and next rule to evaluate will be the 'negative' successor. In effect, we get a binary decision tree, but we don't have to worry about its completeness because it is guaranteed that at least one rule will fire no matter what are the conditions (because the first rule is always true).

Rules were implemented in Boo language using the RhinoDSL library from the Rhino-tools package. Rhino DSL is a library for building DSLs (domain specific languages) in Boo. Here's a link to its author's blog: http://ayende.com/Blog/archive/2007/12/03/Implementing-a-DSL.aspx. The guy has done a great work and many interesting examples of DSLs can be found there.

Below is an example ruleset in my "rule definition language". BTW, it's also a valid Boo script:


Ruleset "MyRules"


rule "R1", "R2", null, V.Counter < 9:
log.Info("AAA");

rule "R2", "R3", null, V.Counter < 8:
log.Info ("R2")

rule "R3", "R4", null, V.Counter < 7:
log.Info ("R3")

rule "R4", null, "R5", V.Counter == 1:
log.Info ("R4")

rule "R5", "R6", null, 1 == 1:
log.Info ("R5: Counter is ${V.Counter}")

rule "R6", "X", null, 2 % 2 == 0:
log.Info ("Rule six: {0}", date.Now)

rule "X", null, null, date.Today > date.Parse('2008-10-11'):
log.Warn("The X Rule!!!")


Sorry for the formatting, I'll fix that in spare time. And a short explanation of what each 'rule' means.
'rule' keyword defines a new rule. It has 5 parameters:
  • rule Id
  • id of positive successor rule (null if there is no successor)
  • id of negative successor rule (null if there is no successor)
  • condition
  • and action (action starts in new line, after last colon - because Boo allows such syntax).

So this entry:

rule "R6", "X", null, 2 % 2 == 0:
log.Info ("Rule six: {0}", date.Now)

means 'define rule R6 that will fire if expression "2 % 2 == 0" evals to true. If it is true, execute action that writes current date to log file. Next rule to evaluate will be "X", or none if the rule doesn't fire'

Currently rules engine is a completely standalone project, but I plan to integrate it into NGinn process engine.It will be used in many places, certainly as a part of process logic, but also for message routing and preprocessing. 

The main problem is that Boo is not yet used in NGinn, except for the RippleBoo project. Currently Script.Net language is the main script environment for NGinn processes and I wouldn't like to mix these two languages. So probably only one is here to stay, and chances are it will be Boo. Script.Net is more elastic and easier to use, but Boo is more mature, better tested and documented. Main issue with Boo is that it's a compiled language, so it will require more effort to integrate it with NGinn engine which is very 'dynamic' in nature.