cloj-rules-engine is a very simple rules engine written in Clojure and designed to work with Java.
- Maven:
pom.xml:
<repositories>
<repository>
<id>clojars</id>
<url>http://clojars.org/repo/</url>
</repository>
</repositories>
<dependency>
<groupId>clojars.org</groupId>
<artifactId>cloj-rules-engine</artifactId>
<version>0.1.2-SNAPSHOT</version>
</dependency>
java code:
...
cloj_rules_engine.ClojRules clrules = new cloj_rules_engine.ClojRules();
...
clrules.initialize("rules.clj");
...
- Clojure:
project.clj:
[clojars.org/cloj-rules-engine "0.1.2-SNAPSHOT"]
clojure code:
(ns ...
(:require [cloj-rules-engine.rules-mng :as cre]
...))
...
(cre/initialize "RULES_PATH")
...
Table of Contents
- Description
- Features
- Main methods
- Things to do / limitations / tips
- Features
- Prerequisites
- Usage
- Complex Rules
- License
cloj-rules-engine is a rules engine written in Clojure.
-
Each rule has a condition (composed of conditional expressions -written in Clojure- that refer to facts), and a set of actions that are activated if the condition is satisfied. Facts are the data upon which rules operate. The fired actions are a a set of string identifiers.
-
Rules are expressed in a simple and easy to read Clojure format (clojure maps)
{
:RULE_1 {:cond "(and (< #A 10) (> #B 50))"
:actions ["action-1"]}
...
}
- Rules with 'probabilities': if condition is satisfied, then the actions have a probability of being fired. These probabilities are values between 0 (no chance to be fired - 0%) and 1 (will be fired - 100%). These actions are evaluated in order. This means that if an action is fired, the rest of the actions are ignored.
:RULE_2 {:cond "(> #A 10)"
:actions [{"action-B" 0.5} {"action-C" 0.5}]
- Facts can be expressed via clojure maps (Clojure) or via PersistentArrayMap objects (Java):
(update-map-facts {"#A" "14"})
PersistentArrayMap facts_map = new PersistentArrayMap(new Object[] {
"#A", "14"
});
clrules.updateMapFacts(facts_map);
-
This library can be used from Java or Clojure code
-
Third party libraries used in this project:
Libs | Version | License |
---|---|---|
clojure | 1.8.0 | |
tools.logging | 0.3.1 | |
log4j | 1.2.17 | |
data.json | 0.2.6 | |
proto-repl | 0.3.1 | |
math.numeric-tower | 0.0.4 | |
lein-cloverage | 1.0.9 |
- initialize loads rules map from absolute or relative path. Returns true if everything is okay.
(initialize "rules.clj")
clrules.initialize("rules.clj");
- update-map-facts update / initialize facts
(update-map-facts {"#A" "14"})
clrules.updateMapFacts(facts_map);
- get-rules-actions evaluates rules based on current facts, and return a list (String) of 'fired' actions
(get-rules-actions)
clrules.getRulesActions();
-
get-fired-rules
-
initialize-from-json
-
get-rules-actions-probs (ONLY Rules with 'probabilities') valuates rules based on current facts, and return a list (String) of 'fired' actions
-
(RULES DEFINITION) The set of rules are defined using Clojure syntax => Clojure maps. Parameters / facts have the following formatt: #FACTNAME
- No underscores allowed.
- A hash (#) before the fact name
- Regular expression used to validate fact / parameter names:
#"\#[A-Za-z][A-Za-z0-9]*"
-
(RULES EVALUATION) The rules and facts are evaluated following the steps of the next example:
-
In this example we have 2 facts or parameters: #A and #B
-
First, we create a new file to define the rules conditions:
:RULE_1 {:cond "(and (< #A 10) (> #B 50))" :actions ["action-1"]}
- Then, when using the library, we set or update the facts
(update-map-facts {"#A" 33, "#B" 66}))
- After the new facts' values have been updated, the rules conditions are transformed to clojure syntax in the following way:
(when (and (< 33 10) (> 66 50)) :RULE_1)
- Finally, if condition is satisfied (using clojure eval function inside get-rules-actions method), the rule is tagged as fired
-
-
(RULES DEFINITION) Conditions are clojure expressions surrounded by quotes.
-
(RULES DEFINITION) When creating the set of rules, use a hash for each of the parameters / facts (i.e. #A and #B):
:RULE_1 {:cond "(and (< #A 10) (> #B 50))"
:actions ["action-1"]}
- (RULES DEFINITION) Use
str
function, single quotes or double quotes (and escape character) if you want to eval String variables.
:RULE_5 {:cond "(= (str #D) (str 50))"
:actions ["action-E"]}
:RULE_6 {:cond "(= #D \"goldenaxe\")"
:actions ["action-F"]}
:RULE_7 {:cond "(= #D 'goldenaxe2')"
:actions ["action-G"]}
- (TESTING FACTS) When creating / updating facts, escape string values that will be used as string
(update-map-facts {"#A" 15, "#D" "\"goldenaxe\""}))
- If a rule is evaluated and 'fired', it won't be fired until facts are updated. In order to get all the 'fired' rules, call the get-fired-rules method / function
-
Java version 8
-
Leiningen 2.0.0 or above installed.
First, define a set of rules ("rules.clj"):
{
:RULE_1 {:cond "(and (< #A 10) (> #B 50))"
:actions ["action-1"]
:desc "Rule description: 'launch' action-1 if 'a' is lower than 10 and if 'b' is greater than 50"}
:RULE_2 {:cond "(> #A 10)"
:actions ["action-2"]}
}
And then, ...
(initialize "rules.clj")
(update-map-facts {"#A" "14"})
(get-rules-actions)
(get-fired-rules)
Or...
(if (initialize "rules.clj")
(when (update-map-facts {"#A" "15", "#B" 13, "#D" "\"goldenaxe\""})
(get-rules-actions))
false)
-
Java code to use the library:
cloj_rules_engine.ClojRules clrules = new cloj_rules_engine.ClojRules();
...
clrules.initialize("rules.clj");
PersistentArrayMap facts_map = new PersistentArrayMap(new Object[] {
"#A", "5",
"#B", "51"
});
clrules.updateMapFacts(facts_map);
clrules.getRulesActions();
clrules.getFiredRules(); // get fired rules in json format
- You can use functions from clojure.math.numeric-tower when defining rules:
expt
,abs
,gcd
,lcm
,floor
...
:RULE_8 {:cond "(> (sqrt #C) 10)"
:actions ["action-H-sqrt"]
:desc "Rule description: 'launch' action-H-sqrt if square root of #C is greater than 10."}
- You can also use clojure functions (from org.clojure/clojure) that return boolean values:
every?
,even?
,odd?
...
:RULE_9 {:cond "(every? even? (list #A #B #C))"
:actions ["action-I-even?"]
:desc "Rule description: 'launch' action-I-even? if all elements from list are even."}
- Or custom functions:
#(> % 10)
:RULE_10 {:cond "(every? #(> % 10) [#A #B #C])"
:actions ["action-J-func"]
:desc "Rule description: 'launch' action-J-func if all elements from list / vector are greater than 10"}
- (warning: not ready yet - only works with string vectors or lists) Use lists or vectors as parameters:
:RULE_11 {:cond "(every? #(> % 100) #LIST1)"
:actions ["action-K-func"]
:desc "Rule description: 'launch' action-K-func if all elements from list / vector '#LIST1' are greater than 10"}
(update-map-facts {"#A" "21", "#B" 43, "#C" 1000, "#LIST1" "[121 321 123 122 1233]"})
Copyright © 2017 Roi Sucasas Font
Distributed under the Eclipse Public License, the same as Clojure.