wtf.metio.reguloj:reguloj

Lightweight business rule engine

https://github.com/metio/reguloj

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (12.6%) to scientific vocabulary

Keywords

java rule-engine
Last synced: 4 months ago · JSON representation ·

Repository

Lightweight business rule engine

Basic Info
  • Host: GitHub
  • Owner: metio
  • License: 0bsd
  • Language: Java
  • Default Branch: main
  • Homepage:
  • Size: 412 KB
Statistics
  • Stars: 26
  • Watchers: 4
  • Forks: 5
  • Open Issues: 0
  • Releases: 41
Topics
java rule-engine
Created about 12 years ago · Last pushed almost 2 years ago
Metadata Files
Readme Contributing License Code of conduct Citation Security Authors

README.md

reguloj Chat

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:

```java // All rules will be evaluated indefinitely until no further rule fires. RuleEngine chained = RuleEngine.chained();

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

// Evaluates all rules, stops after the first one that fires. RuleEngine 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:

```java // setup - more details later RuleEngine engine = ...; Collection> 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 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:

```java Rule rule = Rule.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, however be aware that some additional type information is required in this case:

```java Rule rule = Rule.when(context -> context.check()) .then(context -> context.action())

Rule rule = Rule.when((CONTEXT context) -> context.check()) .then(context -> context.action()) ```

In case you want to create a Rule that always fires/runs, use the following shortcut:

```java // using predefined consumer Rule rule = Rule.always(consumer1.andThen(consumer2));

// using lambda Rule rule = Rule.always((CONTEXT 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.

java 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:

```java public record Cart(List topic, List prices) implements Context> {

} ```

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:

```java 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:

java 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

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

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

java final var standardPrice = Rule .when((Cart 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 .when((Cart 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:

java 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:

java 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:

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

Using RuleEngine#limited

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

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

java final var standardPrice = Rule .when((Cart cart) -> cart.topic().size() == 1) // fires for single products .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 100))); final var reducedPrice = Rule .when((Cart 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.

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

```java 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

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

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

java final var standardPrice = Rule .when((Cart cart) -> cart.topic().size() == 1 && cart.prices().size() == 0) .then(cart -> cart.prices().add(new Price(TEST_PRODUCT, 100))); final var reducedPrice = Rule .when((Cart 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.

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

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

```java 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 exactly the same again.

Integration

xml <dependency> <groupId>wtf.metio.reguloj</groupId> <artifactId>reguloj</artifactId> <version>${version.reguloj}</version> </dependency>

kotlin 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 | |------------|------| | 2022.4.12+ | 17+ | | 2021.4.13+ | 16+ |

Alternatives

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

License

``` Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ```

Owner

  • Name: metio.wtf
  • Login: metio
  • Kind: organization

Citation (CITATION.cff)

# SPDX-FileCopyrightText: The reguloj Authors
# SPDX-License-Identifier: 0BSD

cff-version: 1.2.0
title: reguloj
message: If you use this software, please cite it as below.
type: software
authors:
- family-names: Hoß
  given-names: Sebastian
  email: seb@hoß.de
url: https://github.com/metio/reguloj
license: 0BSD
keywords:
  - business
  - engine
  - java
  - make
  - maven
  - rule

GitHub Events

Total
  • Watch event: 1
  • Fork event: 1
Last Year
  • Watch event: 1
  • Fork event: 1

Issues and Pull Requests

Last synced: 5 months ago

All Time
  • Total issues: 16
  • Total pull requests: 87
  • Average time to close issues: almost 4 years
  • Average time to close pull requests: 9 days
  • Total issue authors: 1
  • Total pull request authors: 3
  • Average comments per issue: 0.0
  • Average comments per pull request: 0.07
  • Merged pull requests: 80
  • Bot issues: 0
  • Bot pull requests: 51
Past Year
  • Issues: 0
  • Pull requests: 4
  • Average time to close issues: N/A
  • Average time to close pull requests: 2 minutes
  • Issue authors: 0
  • Pull request authors: 1
  • Average comments per issue: 0
  • Average comments per pull request: 0.0
  • Merged pull requests: 4
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • sebhoss (16)
Pull Request Authors
  • dependabot[bot] (36)
  • sebhoss (33)
  • github-actions[bot] (15)
Top Labels
Issue Labels
enhancement (1) question (1)
Pull Request Labels
enhancement (37) dependencies (36) bug (2)

Packages

  • Total packages: 1
  • Total downloads: unknown
  • Total dependent packages: 1
  • Total dependent repositories: 2
  • Total versions: 42
repo1.maven.org: wtf.metio.reguloj:reguloj

Lightweight business rule engine

  • Versions: 42
  • Dependent Packages: 1
  • Dependent Repositories: 2
Rankings
Dependent repos count: 16.0%
Average: 32.6%
Dependent packages count: 32.7%
Stargazers count: 36.1%
Forks count: 45.8%
Last synced: 4 months ago

Dependencies

pom.xml maven
  • org.mockito:mockito-core test
.github/actions/managed-java/action.yml actions
  • actions/setup-java v3 composite
.github/actions/managed-maven/action.yml actions
  • actions/setup-java v3 composite
.github/workflows/codeql-analysis.yml actions
  • actions/cache v3 composite
  • actions/checkout v3 composite
  • actions/setup-java v3 composite
  • github/codeql-action/analyze v2 composite
  • github/codeql-action/autobuild v2 composite
  • github/codeql-action/init v2 composite
.github/workflows/release.yml actions
  • ./.github/actions/managed-maven * composite
  • actions/cache v3 composite
  • actions/checkout v3 composite
  • dawidd6/action-send-mail v3.7.1 composite
  • s3krit/matrix-message-action v0.0.3 composite
  • softprops/action-gh-release v1 composite
  • timheuer/base64-to-file v1.2 composite
.github/workflows/reuse.yml actions
  • actions/checkout v3 composite
  • fsfe/reuse-action v1 composite
.github/workflows/update-parent.yml actions
  • ./.github/actions/managed-java * composite
  • actions/cache v3 composite
  • actions/checkout v3 composite
  • peter-evans/create-pull-request v4 composite
  • peter-evans/enable-pull-request-automerge v2 composite
.github/workflows/verify.yml actions
  • actions/cache v3 composite
  • actions/checkout v3 composite
  • actions/setup-java v3 composite