Skip to content

rsucasasf/cloj-rules-engine

Repository files navigation

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.

  • 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

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 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 License
tools.logging 0.3.1 License
log4j 1.2.17 License
data.json 0.2.6 License
proto-repl 0.3.1 License: MIT
math.numeric-tower 0.0.4 License
lein-cloverage 1.0.9 License

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 / tips

  • (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:

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

    2. First, we create a new file to define the rules conditions:

    :RULE_1 {:cond "(and (< #A 10) (> #B 50))"
             :actions ["action-1"]}
    1. Then, when using the library, we set or update the facts
    (update-map-facts {"#A" 33, "#B" 66}))
    1. 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)
    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

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.

About

Rules engine written in Clojure

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •