All Projects → metio → reguloj

metio / reguloj

Licence: CC0-1.0 License
Lightweight business rule engine

Programming Languages

java
68154 projects - #9 most used programming language
Makefile
30231 projects

Projects that are alternatives of or similar to reguloj

liteflow
Small but powerful rules engine,轻量强大优雅的规则引擎
Stars: ✭ 1,119 (+4562.5%)
Mutual labels:  rule-engine
arete
Fast, Clojure-based rule engine
Stars: ✭ 25 (+4.17%)
Mutual labels:  rule-engine
evrete
Evrete is a lightweight and intuitive Java rule engine.
Stars: ✭ 37 (+54.17%)
Mutual labels:  rule-engine
RulerZBundle
Symfony Bundle for RulerZ
Stars: ✭ 38 (+58.33%)
Mutual labels:  rule-engine
rule-engine
基于流程,事件驱动,可拓展,响应式,轻量级的规则引擎。
Stars: ✭ 165 (+587.5%)
Mutual labels:  rule-engine
claire
Continuously Learning Artificial Intelligence Rules Engine (Claire) for Smart Homes
Stars: ✭ 18 (-25%)
Mutual labels:  rule-engine
powerflows-dmn
Power Flows DMN - Powerful decisions and rules engine
Stars: ✭ 46 (+91.67%)
Mutual labels:  rule-engine
IoT-Technical-Guide
🐝 IoT Technical Guide --- 从零搭建高性能物联网平台及物联网解决方案和Thingsboard源码分析 ✨ ✨ ✨ (IoT Platform, SaaS, MQTT, CoAP, HTTP, Modbus, OPC, WebSocket, 物模型,Protobuf, PostgreSQL, MongoDB, Spring Security, OAuth2, RuleEngine, Kafka, Docker)
Stars: ✭ 2,565 (+10587.5%)
Mutual labels:  rule-engine
TabInOut
Framework for information extraction from tables
Stars: ✭ 37 (+54.17%)
Mutual labels:  rule-engine
devs
Devs是一款轻量级的规则引擎
Stars: ✭ 15 (-37.5%)
Mutual labels:  rule-engine
SFDCRules
Simple yet powerful Rule Engine for Salesforce - SFDCRules
Stars: ✭ 38 (+58.33%)
Mutual labels:  rule-engine
mmqtt
An Open-Source, Distributed MQTT Broker for IoT.
Stars: ✭ 58 (+141.67%)
Mutual labels:  rule-engine
naive-rete
Python RETE algorithm
Stars: ✭ 51 (+112.5%)
Mutual labels:  rule-engine
rools
A small rule engine for Node.
Stars: ✭ 118 (+391.67%)
Mutual labels:  rule-engine
hmrb
Python Rule Processing Engine 🏺
Stars: ✭ 65 (+170.83%)
Mutual labels:  rule-engine
cs-expert-system-shell
C# implementation of an expert system shell
Stars: ✭ 34 (+41.67%)
Mutual labels:  rule-engine
SimpleRules
Yet another rules engine, but simpler to use!!
Stars: ✭ 44 (+83.33%)
Mutual labels:  rule-engine
rule-engine
Forward chaining rule engine with an extensible DSL
Stars: ✭ 28 (+16.67%)
Mutual labels:  rule-engine
ruledesigner
Rule Designer is the Eclipse-based development environment for ODM developers.
Stars: ✭ 14 (-41.67%)
Mutual labels:  rule-engine
EngineX
Engine X - 实时AI智能决策引擎、规则引擎、风控引擎、数据流引擎。 通过可视化界面进行规则配置,无需繁琐开发,节约人力,提升效率,实时监控,减少错误率,随时调整; 支持规则集、评分卡、决策树,名单库管理、机器学习模型、三方数据接入、定制化开发等;
Stars: ✭ 369 (+1437.5%)
Mutual labels:  rule-engine

reguloj Chat Mailing List

reguloj is a small and lightweight Java rule engine.

Usage

Creating rule engines

A rule engine evaluates a set of rules in a specific context. The RuleEngine interface offers 3 factory methods to build rule engines:

// All rules will be evaluated indefinitely until no further rule fires.
RuleEngine<CONTEXT> chained = RuleEngine.chained();

// All rules will be evaluated, but only a maximum number of 5 times.
RuleEngine<CONTEXT> limited = RuleEngine.limited(5);

// Evaluates all rules, stops after the first one that fires.
RuleEngine<CONTEXT> firstWins = RuleEngine.firstWins();

All provided rule engines are thread-safe and can be used as often as you like. If custom inference behavior is required, subclass AbstractRuleEngine and implement the infer() method. The following code example shows how to work with rule engines:

// setup - more details later
RuleEngine<CONTEXT> engine = ...;
Collection<Rule<CONTEXT>> rules = ...;
CONTEXT context = ...;

// true if at least one rule can fired.
engine.analyze(rules, context);

// perform conclusions of those rules that fired.
engine.infer(rules, context);

Note that the order of the collection dictates the evaluation order of your rules - if order does matter, use List rather than Set as a Collection implementation.

Creating rules

A rule has a name and runs in a given context. Additionally, it can be checked whether a rule fires in a given context.

Either implement the Rule interface yourself and or use the supplied rule implementation and builder. A standard rule is composed of a java.util.function.Predicate and java.util.function.Consumer. Both interfaces require you to implement only a single method and do not restrict you in any way. Complex rules can be created by grouping or chaining predicates/consumers together with the help of several utility methods. The following example creates a rule composed of 2 predicates and 2 consumers:

Rule<CONTEXT> rule = Rule.called(name)
                .when(predicate1.and(predicate2))
                .then(consumer1.andThen(consumer2));

// true if the rule would fire in the given context, e.g. the above predicate is true.
rule.fires(context);

// runs (applies) the rule in the given context
rule.run(context);

Using Java 8 lambdas is possible as well:

Rule<CONTEXT> rule = Rule.called(name)
                .when(context -> context.check())
                .then(context -> context.action())

Note that custom implementations of the Rule interface don't necessary have to use the java.util.function package and are free to choose how their implementation looks like.

Creating an inference context

An inference context contains information needed by predicates and/or consumers. This project supplies a simple implementation of the Context interface called SimpleContext which just wraps a given topic. The AbstractContext class can be used to create subclasses in case your rules need extra information. The API acknowledges this by using <CONTEXT extends Context<?>> as type parameter for all methods which expect a Context, thus allowing all context implementations to be used. See item 28 in Effective Java for more details.

CONTEXT context = Context.of("some object");

Example Use Case

The wtf.metio.regoluj.shoppingcart package contains tests for an example use case revolving around shopping carts, products, and their prices. It works as follows:

We have a custom Context implementation in the form of wtf.metio.regoluj.shoppingcart.Cart that holds a list of products, and a matching list of prices for those products. The list of products is its main topic. Various Rules are used to calculate the price per product in the shopping cart. Written as a record, the Cart could look like this:

public record Cart(List<Product> topic, List<Price> prices) implements Context<List<Product>> {

}

As you can see, one of the record parameters must be named topic and use the type of the context in order to correctly implement the method contract of Context. Similar, a Product and Price could look like this:

public record Product(String name) {

}

public record Price(Product product, int price) {

}

The initial state of a card contains just the products without any previously calculated prices in this example:

final Cart singleProductCart = new Cart(List.of(TEST_PRODUCT), new ArrayList<>());
final Cart multiProductCart = new Cart(List.of(TEST_PRODUCT, TEST_PRODUCT), new ArrayList<>());

The constant TEST_PRODUCT is just some example data that represents objects of your actual business domain: Product TEST_PRODUCT = new Product("xPhone 37");.

Using RuleEngine#firstWins

RuleEngine<Cart> ruleEngine = RuleEngine.firstWins();

While using a first-wins RuleEngine, our Ruless could look like this:

final var standardPrice = Rule.<Cart>called("single purchase uses standard price")
    .when(cart -> true) // always fires thus can be used as a fallback
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 100)));
final var reducedPrice = Rule.<Cart>called("multiple purchases get reduced price")
    .when(cart -> cart.topic().size() > 1) // only fires for multiple products
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));

As you can see, we kept the implementation of the rules rather simple, in order to keep the example focused on the reguloj related classes. In a real world project, you don't want to specify a constant price for a single product, but rather use some database lookup or similar technique to calculate prices more dynamically. Since we need both a Context and a Collection of rules, we combine the above into a List with:

Collection<Rule<Cart>> rules = List.of(reducedPrice, standardPrice);

The order is important here - we first test if we can apply the reduced priced, and only apply the full price as a fallback. In order to infer a price for our shopping carts, combine Rules and Context (carts) using the previously built RuleEngine as the following example shows:

ruleEngine.infer(rules, singleProductCart);
ruleEngine.infer(rules, multiProductCart);

Since the above rules will only ever add one price, we can check whether everything works as expected like this:

Assertions.assertEquals(100, singleProductCart.prices().get(0).price())
Assertions.assertEquals(150, multiProductCart.prices().get(0).price())

Using RuleEngine#limited

RuleEngine<Cart> ruleEngine = RuleEngine.limited(1);

While using a limited RuleEngine, our Ruless could look like this:

final var standardPrice = Rule.<Cart>called("single purchase uses standard price")
    .when(cart -> cart.topic().size() == 1) // fires for single products
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 100)));
final var reducedPrice = Rule.<Cart>called("multiple purchases get reduced price")
    .when(cart -> cart.topic().size() > 1) // fires for multiple products
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));

The difference here is that the first rule only fires for carts that contain a single product (remember the topic of a cart is a list of products) since a limited RuleEngine will try ever rule a limited number of times and thus it won't stop after some rule fired as in the first example. Note that this implementation would have worked in the first example as well, however the first example would not work with a limited RuleEngine. The implementation for the second rule is exactly the same as the first example.

Collection<Rule<Cart>> rules = Set.of(standardPrice, reducedPrice);

Since the order in which rules are fired does not matter, we can use a Set rather than List. In case you are planning on creating rules dynamically based on some external data, like XML, YAML, a database, or your neighbours dog, make sure to be a specific as possible in your predicates in order to make your rules as widely usable as possible.

ruleEngine.infer(rules, singleProductCart);
ruleEngine.infer(rules, multiProductCart);

Assertions.assertEquals(100, singleProductCart.prices().get(0).price())
Assertions.assertEquals(150, multiProductCart.prices().get(0).price())

Running the inference process is exactly the same no matter which RuleEngine you picked or how you Rules are implemented.

Using RuleEngine#chained

RuleEngine<Cart> ruleEngine = RuleEngine.chained();

While using a chained RuleEngine, our Ruless could look like this:

final var standardPrice = Rule.<Cart>called("single purchase uses standard price")
    .when(cart -> cart.topic().size() == 1 && cart.prices().size() == 0)
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 100)));
final var reducedPrice = Rule.<Cart>called("multiple purchases get reduced price")
    .when(cart -> cart.topic().size() > 1 && cart.prices().size() == 0)
    .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));

Since chained RuleEngines will run all Rules as often as they fire, we need an extra terminal condition to stop re-firing our rules. Since we are only calculating the price of a single product, we can always stop firing our Rules in case there is already a price in our cart.

Collection<Rule<Cart>> rules = Set.of(standardPrice, reducedPrice);

Again, the order of our rules do not matter, thus we are using a Set.

ruleEngine.infer(rules, singleProductCart);
ruleEngine.infer(rules, multiProductCart);

Assertions.assertEquals(100, singleProductCart.prices().get(0).price())
Assertions.assertEquals(150, multiProductCart.prices().get(0).price())

Getting a final price for our carts is exatly the same again.

Integration

<dependency>
  <groupId>wtf.metio.reguloj</groupId>
  <artifactId>reguloj</artifactId>
  <version>${version.reguloj}</version>
</dependency>
dependencies {
    implementation("wtf.metio.reguloj:reguloj:${version.reguloj}") {
        because("we want to use a lightweight rule engine")
    }
}

Replace ${version.reguloj} with the latest release.

Requirements

regoluj Java
2021.4.13+ 16+

Alternatives

In case reguloj is not what you are looking for, try these projects:

License

To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.

You should have received a copy of the CC0 Public Domain Dedication along with
this software. If not, see http://creativecommons.org/publicdomain/zero/1.0/.

Mirrors

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].