rule_engine 0.0.4

  • README.md
  • CHANGELOG.md
  • Installing
  • Versions
  • new62

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:

dependencies:
 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>"
  when
      <clause>
      <clause>
      ...
      <clause>
  then
      <consequence>
end

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"
  when
      Expense( category == "Cheese" )
      not Expense( amount > 10, category == "Cheese" )
  then
      publish Achievement( "no expense over 10 usd" )
end

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"
  when
      SimpleFact( name == "Bob", created in Window( length: Duration(days: 31) ), $amount: amount )
  then
      insert Achievement( "Bob saved some money", $amount )
end
""";

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 DateTime.now()) );

FAQ

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:


dependencies:
  rule_engine: ^0.0.4

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or flutter packages 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';
  
Version Uploaded Documentation Archive
0.0.4 Sep 4, 2018 Go to the documentation of rule_engine 0.0.4 Download rule_engine 0.0.4 archive
0.0.3 Aug 31, 2018 Go to the documentation of rule_engine 0.0.3 Download rule_engine 0.0.3 archive
0.0.2 Aug 30, 2018 Go to the documentation of rule_engine 0.0.2 Download rule_engine 0.0.2 archive
0.0.1 Aug 28, 2018 Go to the documentation of rule_engine 0.0.1 Download rule_engine 0.0.1 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
29
Health:
Code health derived from static analysis. [more]
99
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
62
Learn more about scoring.

We analyzed this package on Sep 18, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0
  • pana: 0.12.3

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:rule_engine/rule_engine.dart.

Suggestions

Package is pre-v0.1 release.

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

Fix lib/src/parser.dart.

Analysis of lib/src/parser.dart reported 2 hints:

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

line 60 col 9: The value of the local variable 'j' isn't used.

Maintain an example.

None of the files in your example/ directory matches a known example patterns. Common file name patterns include: main.dart, example.dart or you could also use rule_engine.dart.

Dependencies

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