rule_engine 0.0.4

  • Readme
  • Changelog
  • Installing
  • 33

rule_engine #

Build Status

Features #

  • Brings production rules with a drools-like syntax to Dart and Flutter
  • Dynamic insertion and evaluation of facts
  • Caches earlier matches of different rule clauses for better performance
  • Works even with lack of reflection in Flutter
  • Allows multiple callbacks for end results
  • Supports variables inside rules
  • Supports rolling windows on all DateTime objects
  • Supports aggregates on all numerical attributes (sum, average, min, max)
  • Allows for negation of facts by offering a not keyword

Getting Started #

Get started by adding the package to your project as a dependency:

 rule_engine: ^0.0.3

Rule syntax #

This rule engine follows basically the same syntax as Drools, with some minor differences. It has the following boilerplate:

rule "<name>"

A detailed overview of the syntax in BNF and with railroad diagrams can be found in syntax.xhtml.

Clause syntax #

Each clause has a type and multiple attributes. All those have to match an object before the clause can be true.

SimpleFact( name == "Bob", created in Window( length: Duration(days: 31) ), $amount: amount )

In the above case, there are 2 condition and one assignment in the clause. The first condition requires the name of an SimpleFact-object to be Bob. The second uses the DateTime-attribute created to assert if it was created in the last 31 days.

So each clause matches one type of fact, followed by zero or more conditions or assignments between the brackets. In short, the following elements are available:

  • Assignments: the right hand side is assigned to a symbol, which starts with a $.

    SimpleFact( $amount: amount )

    In the above clause, the attribute amount is assigned to a symbol with name $amount.

    For convenience, the rule name is by default available as $ruleName, this can be overwritten. Support for additional variables is planned, but not yet implemented.

  • Conditions: both sides have to be comparable, and the condition should be true for the clause to finish. The environment supports equality for strings, numbers and objects and comparisons for numbers.

    SimpleFact( amount > 10 )

    The above clause matches all objects of type SimpleFact with an amount of more than 10 dollars/euros/turtles.

    SimpleFact( name == "Bob", $bobsSpending: amount )
    SimpleFact( amount > $bobsSpending )

    Conditions can support symbols as well, becoming available in sequential order.

  • Windows: to support sliding windows, an in-operator is available for attributes of type DateTime. This window follows the dart syntax and supports a start and end date and a duration.

    SimpleFact( created in Window( length: Duration( days: 31 ) ) )

    This is the most simple syntax and takes a window starting 31 days ago and ending now. The length can be defined with a duration object, just like in dart.

    SimpleFact( created in Window( start: "1969-07-20 00:00:00", length: Duration( days: 31 ) ) )
    SimpleFact( created in Window( end: "2018-07-20 23:59:59",length: Duration( days: 31 ) ) )

    As is seen in the above example, both start and end times can be specified. Currently, this is as a string that can be parsed by dart's DateTime constructor. Future work is to provide the entire dateTime-api, but feel free to fork it. ;-)

  • Aggregates: when multiple facts match one clause, these can be combined to one evaluation. The result can be used as a regular condition, or an assignment. Inside the aggregate, only attributes are allowed.

    SimpleFact( sum( amount ) > 1000 )

    This will evaluate to true if the sum of all matching facts is over 1000. In addition to the sum() operation, min(), max() (not yet implemented) and average() are available as well.

In addition to these elements of a clause, they can also be negated. This means a clause will fail if a fact matching the entire clause is present, allowing it to halt the entire rule.

rule "weekly saver"
      Expense( category == "Cheese" )
      not Expense( amount > 10, category == "Cheese" )
      publish Achievement( "no expense over 10 usd" )

Consequence syntax #

Consequences are either inserted as new facts, allowing rules to generate additional facts, or published to a listener. At the moment, only publishing is supported by using the publish keyword.

publish Achievement( "Bob saved some money", $amount )

So consequences start with the insert or publish keyword, followed by the type of object and a list of attributes, like in the constructor. As seen in the above example, declared symbols are available from of the rule symbol table.

Example #

Start by creating a new rule engine with a listener. The code attribute is a string, which can be predefined or read in from a file.

String code = r"""
rule "get amount for bob"
      SimpleFact( name == "Bob", created in Window( length: Duration(days: 31) ), $amount: amount )
      insert Achievement( "Bob saved some money", $amount )

RuleEngine ruleEngine = new RuleEngine(code);

//You can register multiple listeners, which are all called in the order they are registered
ruleEngine.registerListener((type, arguments) {
  print("insert $type with arguments $arguments");

//insert a fact that implements from [Fact]
ruleEngine.insertFact( new SimpleFact("Bob", 1000, "Cheese", new );


Why a rule engine for dart? #

Rule engines are very suitable for writing business logic in a central location. This makes it easier to maintain and usually less like spaghetti code. In our case, we wanted a rule engine for checking if a user has earned an achievement. This makes it easy to separate each achievement as a separate rule, without impacting the flow of the application.

In the case of dart, no public rule engine was/is available at the moment this project started.

What's up with that inheritance? #

Dart—or at Flutter to be precise—has no support for reflection, which is needed for autonomous evaluation of those facts, and also to create new objects as consequences. Since this is aimed at Flutter, this uses inheritance. One benefit, no need to worry about what is accessible and what not, since you decide explicitly.

Are there alternatives? #

For dart, at the moment (aug. 2018), no. For other languages, yes. A totally incomplete list of some I've used:

  • Drools: For Java, probably the most known engine. Uses traditionally a very efficient implementation of RETE, or even LEAPS in newer releases.

  • CHR: A rule rewriting system, implementations for Prolog, Java, C, C++, JavaScript, ... are available.

[0.0.4] - 04/09/2018

  • Support for empty clauses SimpleFact()
  • Support for floating point numbers

[0.0.3] - 31/08/2018

  • Added one environmental symbol ($ruleName)
  • Removed unnecessary output

[0.0.2] - 30/08/2018

  • Fixed incorrect behavior of not-operator with multiple types of facts.
  • Improved code style.
  • Fixed an issue with documentation

[0.0.1] - 20/08/2018

  • Initial version, with support for aggregates and rolling windows.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:

  rule_engine: ^0.0.4

2. Install it

You can install packages from the command line:

with pub:

$ pub get

with Flutter:

$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:

import 'package:rule_engine/rule_engine.dart';
Describes how popular the package is relative to other packages. [more]
Code health derived from static analysis. [more]
Reflects how tidy and up-to-date the package is. [more]
Weighted score of the above. [more]
Learn more about scoring.

We analyzed this package on Jan 17, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.0
  • pana: 0.13.4

Health issues and suggestions

Document public APIs. (-0.45 points)

105 out of 107 API elements have no dartdoc comment.Providing good documentation for libraries, classes, functions, and other API elements improves code readability and helps developers find and use your API.

Fix lib/src/parser.dart. (-18.98 points)

Analysis of lib/src/parser.dart reported 42 hints, including:

line 1 col 8: Unused import: 'dart:collection'.

line 30 col 7: DO use curly braces for all flow control structures.

line 32 col 7: DO use curly braces for all flow control structures.

line 37 col 7: DO use curly braces for all flow control structures.

line 39 col 7: DO use curly braces for all flow control structures.

Fix lib/src/lexer.dart. (-7.71 points)

Analysis of lib/src/lexer.dart reported 16 hints, including:

line 10 col 21: Unnecessary new keyword.

line 31 col 7: DO use curly braces for all flow control structures.

line 33 col 7: DO use curly braces for all flow control structures.

line 38 col 7: DO use curly braces for all flow control structures.

line 40 col 7: DO use curly braces for all flow control structures.

Fix lib/src/rule.dart. (-2.96 points)

Analysis of lib/src/rule.dart reported 6 hints, including:

line 13 col 16: Unnecessary new keyword.

line 14 col 21: Unnecessary new keyword.

line 19 col 24: Unnecessary new keyword.

line 24 col 35: Unnecessary new keyword.

line 25 col 41: Unnecessary new keyword.

Fix additional 6 files with analysis or formatting issues. (-6.97 points)

Additional issues in the following files:

  • lib/rule_engine.dart (3 hints)
  • lib/src/clause.dart (3 hints)
  • lib/src/window.dart (3 hints)
  • lib/src/consequence.dart (2 hints)
  • lib/src/nodes/aggregate_node.dart (2 hints)
  • lib/src/condition.dart (1 hint)

Maintenance suggestions

Package is getting outdated. (-36.71 points)

The package was last published 71 weeks ago.

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Maintain an example.

None of the files in the package's example/ directory matches known example patterns.

Common filename patterns include main.dart, example.dart, and rule_engine.dart. Packages with multiple examples should provide example/

For more information see the pub package layout conventions.


Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.19.0 <3.0.0
Dev dependencies
test >=0.19.0 <3.0.0