One Ring - Scripting Rules Engine Service


oneRingLogo.png
One Ring isn't like other "Rules Engines", it's meant to be used as a web service (SOA) for multiple applications to gain access to scripted processing of arbitrary parameters.

It centralises processing of common rules (or business rules) for multiple applications that need access to the same rules. Rules are defined using a simple language understood by domain experts.

One Ring is aimed at continuous processing for multiple small applications, not batch processing of billions of entities. It is very light weight and is deployed as a WAR inside a container like Tomcat. You can have multiple One Ring servers running in the same container pointing to different, or the same rule sets.

It has not been optimised for speed, but it's not exactly slow. The engine can process a 1000 simple 3 rule rule sets in around 200ms or ~0.2ms per rule set. That's serially called with fact set-up in each call.

Features

  • A friendly to quite a few humans DSL
  • REST and SOAP interfaces
  • JSON and XML fact encoding
  • Inbuilt rule testing in the rule set
  • Script rules in simplified or not so simplified Groovy
  • Simple directory of ruleset files which can be version controlled using any version control system like Git, Mercurial, Subversion etc.
  • Live updating of rulesets without interruption

Still to come

  • User management and security - at the moment it is assumed you run this server partitioned from external access, however the ability to update and access the servers REST or SOAP functionality could be secured to allow B2B scripted access.
  • Pluggable DSLs - adding Domain Specific Language processing for specific domains should be a simple thing to do.
  • Scriptable data access/lookup services - rules commonly have data tables, and including those tables in the rules directly is ugly and could be abstracted, it's simple enough to read a file in a One Ring rule script, but accessing other REST services and databases could be simpler.
You can find the source at https://github.com/nerdErg/One-Ring

Getting started

Getting One Ring going is pretty simple Download the WAR file and drop it into a web container like Tomcat and point your browser at http://localhost:8080/rulesEngine (change "localhost" to the host you're Tomcat instance is on).

Check out more details here: One+Ring+install

Rule examples

The following is an example rule set using the simple DSL. This shows three different forms of rules, 1) when, then, otherwise; 2) evaluate; and 3) Plain old Groovy.
ruleset("Means Test") { require(['income', 'expenses']) rule("nett income") { when { nett_income = income - expenses nett_income < 400.00 } then { incomeTest = 'passed' } otherwise { incomeTest = 'failed' } } rule("nett income2") { evaluate { incomeTest2 = nett_income < 400.00 } } rule("nett income3") { if(fact.nett_income > 400) { fact.incomeTest3 = 'rich bugger' } else { fact.incomeTest3 = 'poor bugger' } } test(income: 900, expenses: 501) { incomeTest 'passed' nett_income 399 } }

A couple of things to note from this example:
  1. The rule set takes a Map of Facts
  2. The facts are dynamically typed
  3. The require phrase tells the rules engine that certain facts must exist or it will fail
  4. When, Then and Evaluate rules assume variables are from the map of facts. If you assign a value it will be returned in the map of facts to the caller. Outside of when, then, otherwise and evaluate you need to directly reference facts.
  5. "When" clauses must end in an value that will be judged true (then) or false (otherwise). This uses Groovy Truth, so a blank string evaluates to false.
  6. The test phrase takes input test facts, runs them against the rules, and checks the results.

Rule set variables

You can define a variable outside the rules that can be used inside a rule. e.g:
ruleset("Questions") { require(['code']) def questions = ['428606': 'do you want fries with that?', '428605': 'supersize that coke for ya?'] rule("Question Punters") { fact.question = questions[fact.code] } test(code: '428606') {question 'do you want fries with that?'} test(code: '428605') {question 'supersize that coke for ya?'} test(code: '428607') {question null} }
Here we define the variable Map "questions" and use it as a lookup table to get the questions associated with the code number.

ruleset("milkshake"){
require(["singer"])
rule("singer is Kelis"){
when { singer =~ /(?i)Kelis/ }
then { boys = 'In the yard' }
otherwise { boys = 'not in the yard' }
}
test(singer: 'Kelis') { boys 'In the yard' }
test(singer: 'kElis') { boys 'In the yard' }
test(singer: 'Snoop Dog') { boys 'not in the yard' }
}
Here we're using a Groovy regex in the rule. If you make a mistake in the code One Ring rules engine will tell you.

ruleset('test') {
require(['value'])
rule('quantity within range') {
when {
value < 1 || value > 10
}
then {
failed = true
message = ["invalid quantity"]
}
otherwise {
failed = false
message = []
}
}
test(value: 0) {
failed true
message (["invalid quantity"])
}
}
This example shows how to test an array that is passed back as a fact. You need to put brackets () around the array ["invalid quantity"].


Copyright © nerdErg Pty Ltd 2012 ABN 20 159 294 989