Liking cljdoc? Tell your friends :D

cloj-rules-engine

Build Status License GitHub release Codecov Clojars Project

cloj-rules-engine is a very simple rules engine written in Clojure and designed to work with Java.


Table of Contents


Description

cloj-rules-engine is a rules engine written in Clojure.

Rules Engine

Features:

  • 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 represented as a set of identifiers (strings).

  • 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) and 1 (will be fired). 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:

LibsVersionLicense
clojure1.8.0License
tools.logging0.3.1License
log4j1.2.17License
data.json0.2.6License
proto-repl0.3.1License: MIT
math.numeric-tower0.0.4License
  • Main methods:

    • 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

Things to do / limitations

  • (RULES DEFINITION) The set of rules are defined using Clojure syntax => Clojure maps. Parameters / facts have the following formatt: #FACTNAME

    • No underscores allowed.
    • 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:

  1. In the example we have 2 facts or parameters: #A and #B

  2. Rules conditions:

:RULE_1 {:cond "(and (< #A 10) (> #B 50))"
         :actions ["action-1"]}
  1. Facts are set or updated
(update-map-facts {"#A" 33, "#B" 66}))
  1. Rules conditions are transformed to clojure syntax in the following way:
(when (and (< 33 10) (> 66 50)) :RULE_1)
  1. If condition is satisfied (using clojure eval function inside get-rules-actions method), 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

Prerequisites

  1. Java version 8

  2. Leiningen 2.0.0 or above installed.


Usage

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, ...

From Clojure

(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)

From Java

  1. Create a jar or add dependency to maven

  2. 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


Complex Rules

: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]"})

License

Copyright © 2017 Roi Sucasas Font

Distributed under the Eclipse Public License, the same as Clojure.

Can you improve this documentation? These fine people already did:
rsucasasf, Roi & roi
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close