GithubHelp home page GithubHelp logo

deliveredtechnologies / rulebook Goto Github PK

View Code? Open in Web Editor NEW
711.0 63.0 125.0 1.53 MB

100% Java, Lambda Enabled, Lightweight Rules Engine with a Simple and Intuitive DSL

Home Page: http://www.deliveredtechnologies.com

License: Apache License 2.0

Java 98.21% XSLT 1.79%
java rules rules-engine lambda dsl

rulebook's Introduction

RuleBook

» A Simple & Intuitive Rules Abstraction for Java
100% Java · Lambda Enabled · Simple, Intuitive DSL · Lightweight


License Maven Central Build Status Coverage Status Paypal

Why RuleBook?

RuleBook rules are built in the way that Java developers think: Java code. And they are executed in the way that programmers expect: In order. RuleBook also allows you to specify rules using an easy to use Lambda enabled Domain Specific Language or using POJOs that you define!

Tired of classes filled with if/then/else statements? Need a nice abstraction that allows rules to be easily specified in a way that decouples them from each other? Want to write rules the same way that you write the rest of your code [in Java]? RuleBook just might be the rules abstraction you've been waiting for!

Got questions? Here are answers to Frequently Asked Questions!

Still not finding what you are looking for? Try the Wiki!

Contents

1 Getting RuleBook

1.1 Building RuleBook

git clone https://github.com/Clayton7510/RuleBook.git
cd RuleBook
./gradlew build

1.2 Maven Central Releases

  • rulebook-core    Maven Central
  • rulebook-spring Maven Central

1.3 Latest Sonatype SNAPSHOT (Development) Release

  • rulebook-core    Sonatype Nexus
  • rulebook-spring Sonatype Nexus

1.4 Adding RuleBook to Your Maven Project

Add the code below to your pom.xml

<dependency>
    <groupId>com.deliveredtechnologies</groupId>
    <artifactId>rulebook-core</artifactId>
    <version>0.12</version>
</dependency>

1.5 Adding RuleBook to Your Gradle Project

Add the code below to your build.gradle

compile 'com.deliveredtechnologies:rulebook-core:0.12'

[Top]

2 Using RuleBook

2.1 A HelloWorld Example

RuleBook ruleBook = RuleBookBuilder.create()
    .addRule(rule -> rule.withNoSpecifiedFactType()
      .then(f -> System.out.print("Hello "))
      .then(f -> System.out.println("World")))
    .build();

...or use 2 rules

RuleBook ruleBook = RuleBookBuilder.create()
    .addRule(rule -> rule.withNoSpecifiedFactType().then(f -> System.out.print("Hello ")))
    .addRule(rule -> rule.withNoSpecifiedFactType().then(f -> System.out.println("World")))
    .build();

now, run it!

ruleBook.run(new FactMap());

2.2 The Above Example Using Facts

RuleBook ruleBook = RuleBookBuilder.create()
    .addRule(rule -> rule.withFactType(String.class)
      .when(f -> f.containsKey("hello"))
      .using("hello")
      .then(System.out::print))
    .addRule(rule -> rule.withFactType(String.class)
      .when(f -> f.containsKey("world"))
      .using("world")
      .then(System.out::println))
    .build();

..or it could be a single rule

RuleBook ruleBook = RuleBookBuilder.create()
  .addRule(rule -> rule.withFactType(String.class)
    .when(f -> f.containsKey("hello") && f.containsKey("world"))
    .using("hello").then(System.out::print)
    .using("world").then(System.out::println))
  .build();

now, run it!

NameValueReferableMap factMap = new FactMap();
factMap.setValue("hello", "Hello ");
factMap.setValue("world", " World");
ruleBook.run(factMap);

2.3 A [Slightly] More Complex Scenario

MegaBank issues home loans. If an applicant's credit score is less than 600 then they must pay 4x the current rate. If an applicant’s credit score is between 600, but less than 700, then they must pay a an additional point on top of their rate. If an applicant’s credit score is at least 700 and they have at least $25,000 cash on hand, then they get a quarter point reduction on their rate. If an applicant is a first time home buyer then they get a 20% reduction on their calculated rate after adjustments are made based on credit score (note: first time home buyer discount is only available for applicants with a 600 credit score or greater).

public class ApplicantBean {
  private int creditScore;
  private double cashOnHand;
  private boolean firstTimeHomeBuyer;

  public ApplicantBean(int creditScore, double cashOnHand, boolean firstTimeHomeBuyer) {
    this.creditScore = creditScore;
    this.cashOnHand = cashOnHand;
    this.firstTimeHomeBuyer = firstTimeHomeBuyer;
  }

  public int getCreditScore() {
    return creditScore;
  }

  public void setCreditScore(int creditScore) {     
    this.creditScore = creditScore;
  }

  public double getCashOnHand() {
    return cashOnHand;
  }

  public void setCashOnHand(double cashOnHand) {
    this.cashOnHand = cashOnHand;
  }

  public boolean isFirstTimeHomeBuyer() {
    return firstTimeHomeBuyer;
  }

  public void setFirstTimeHomeBuyer(boolean firstTimeHomeBuyer) {
    this.firstTimeHomeBuyer = firstTimeHomeBuyer;
  }
}
public class HomeLoanRateRuleBook extends CoRRuleBook<Double> {
  @Override
  public void defineRules() {
    //credit score under 600 gets a 4x rate increase
    addRule(RuleBuilder.create().withFactType(ApplicantBean.class).withResultType(Double.class)
      .when(facts -> facts.getOne().getCreditScore() < 600)
      .then((facts, result) -> result.setValue(result.getValue() * 4))
      .stop()
      .build());

    //credit score between 600 and 700 pays a 1 point increase
    addRule(RuleBuilder.create().withFactType(ApplicantBean.class).withResultType(Double.class)
      .when(facts -> facts.getOne().getCreditScore() < 700)
      .then((facts, result) -> result.setValue(result.getValue() + 1))
      .build());

    //credit score is 700 and they have at least $25,000 cash on hand
    addRule(RuleBuilder.create().withFactType(ApplicantBean.class).withResultType(Double.class)
      .when(facts ->
            facts.getOne().getCreditScore() >= 700 &&
            facts.getOne().getCashOnHand() >= 25000)
      .then((facts, result) -> result.setValue(result.getValue() - 0.25))
      .build());

    //first time homebuyers get 20% off their rate (except if they have a creditScore < 600)
    addRule(RuleBuilder.create().withFactType(ApplicantBean.class).withResultType(Double.class)
      .when(facts -> facts.getOne().isFirstTimeHomeBuyer())
      .then((facts, result) -> result.setValue(result.getValue() * 0.80))
      .build());
    }
}
public class ExampleSolution {
  public static void main(String[] args) {
    RuleBook homeLoanRateRuleBook = RuleBookBuilder.create(HomeLoanRateRuleBook.class).withResultType(Double.class)
      .withDefaultResult(4.5)
      .build();
    NameValueReferableMap facts = new FactMap();
    facts.setValue("applicant", new ApplicantBean(650, 20000.0, true));
    homeLoanRateRuleBook.run(facts);

    homeLoanRateRuleBook.getResult().ifPresent(result -> System.out.println("Applicant qualified for the following rate: " + result));
  }
}

...or nix the ApplicantBean and just use independent Facts

public class HomeLoanRateRuleBook extends CoRRuleBook<Double> {
  @Override
  public void defineRules() {
    //credit score under 600 gets a 4x rate increase
    addRule(RuleBuilder.create().withResultType(Double.class)
      .when(facts -> facts.getIntVal("Credit Score") < 600)
      .then((facts, result) -> result.setValue(result.getValue() * 4))
      .stop()
      .build());

    //credit score between 600 and 700 pays a 1 point increase
    addRule(RuleBuilder.create().withResultType(Double.class)
      .when(facts -> facts.getIntVal("Credit Score") < 700)
      .then((facts, result) -> result.setValue(result.getValue() + 1))
      .build());

    //credit score is 700 and they have at least $25,000 cash on hand
    addRule(RuleBuilder.create().withResultType(Double.class)
      .when(facts ->
        facts.getIntVal("Credit Score") >= 700 &&
        facts.getDblVal("Cash on Hand") >= 25000)
      .then((facts, result) -> result.setValue(result.getValue() - 0.25))
      .build());

    //first time homebuyers get 20% off their rate (except if they have a creditScore < 600)
    addRule(RuleBuilder.create().withFactType(Boolean.class).withResultType(Double.class)
      .when(facts -> facts.getOne())
      .then((facts, result) -> result.setValue(result.getValue() * 0.80))
      .build());
  }
}
public class ExampleSolution {
  public static void main(String[] args) {
    RuleBook homeLoanRateRuleBook = RuleBookBuilder.create(HomeLoanRateRuleBook.class).withResultType(Double.class)
     .withDefaultResult(4.5)
     .build();

    NameValueReferableMap facts = new FactMap();
    facts.setValue("Credit Score", 650);
    facts.setValue("Cash on Hand", 20000);
    facts.setValue("First Time Homebuyer", true);

    homeLoanRateRuleBook.run(facts);

    homeLoanRateRuleBook.getResult().ifPresent(result -> System.out.println("Applicant qualified for the following rate: " + result));
    }
}

2.4 Thread Safety

RuleBooks are threadsafe. However, FactMaps and other implementations of NameValueReferableMap are not. This means that a single instance of a RuleBook can be run in different threads with different Facts without unexpected results. However, using the same exact FactMap across different threads may cause unexpected results. Facts represent data for individual invocations of a RuleBook, whereas RuleBooks represent reusable sets of Rules.

[Top]

3 The RuleBook Domain Specific Language Explained

The RuleBook Java Domain Specific Language (DSL) uses the Given-When-Then format, popularized by Behavior Driven Development (BDD) and associated testing frameworks (e.g. Cucumber and Spock). Many of the ideas that went into creating the RuleBook Java DSL are also borrowed from BDD, including: Sentences should be used to describe rules and Rules should be defined using a ubiquitous language that translates into the codebase.

3.1 Given-When-Then: The Basis of the RuleBook Language

Much like the Given-When-Then language for defining tests that was popularized by BDD, RuleBook uses a Given-When-Then language for defining rules. The RuleBook Given-When-Then methods have the following meanings.

  • Given some Fact(s)
  • When a condition evaluates to true
  • Then an action is triggered

Given methods can accept one or more facts in various different forms and are used as a collection of information provided to a single Rule. When grouping Rules into a RuleBook, facts are supplied to the Rules when the RuleBook is run, so the 'Given' can be inferred.

When methods accept a Predicate that evaluates a condition based on the Facts provided. Only one when() method can be specified per Rule.

Then methods accept a Consumer (or BiConsumer for Rules that have a Result) that describe the action to be invoked if the condition in the when() method evaluates to true. There can be multiple then() methods specified in a Rule that will all be invoked in the order they are specified if the when() condition evaluates to true.

3.2 The Using Method

Using methods reduce the set of facts available to a then() method. Multiple using() methods can also be chained together if so desired. The aggregate of the facts with the names specified in all using() methods immediately preceeding a then() method will be made available to that then() method. An example of how using() works is shown above.

3.3 The Stop Method

Stop methods break the rule chain. If a stop() method is specified when defining a rule, it means that if the when() condition evaluates to true, following the completion of the then() action(s), the rule chain should be broken and no more rules in that chain should be evaluated.

3.4 Working With Facts

Facts can be provided to Rules using the given() method. In RuleBooks, facts are provided to Rules when the RuleBook is run. The facts available to Rules and RuleBooks are contained in a NameValueReferableMap (the base implementation being FactMap), which is a special kind of Map that allows for easy access to the underlying objects contained in facts. The reason why facts exist is so that there is always a reference to the objects that Rules work with - even if say, an immutable object is replaced, the perception is that the Fact still exists and provides a named reference to a representative object.

3.4.1 The Single Fact Convenience Method

Facts really only have a single convenience method. Since the NameValueReferableMap (e.g. FactMap) is what is passed into when() and then() methods, most of the convenience methods around facts are made available in the Map. However, there is one convenience method included in the Fact class... the constructor. Facts consist of a name value pair. But in some cases, the name of the Fact should just be the string value of the object it contains. In those cases, a constructor with a single argument of the type of the object contained in the fact can be used.

3.4.2 The FactMap Convenience Methods

Although the reason for NameValueReferableMaps (commonly referred to as FactMaps) is important, that doesn't mean anyone wants to chain a bunch of boiler plate calls to get to the value object contained in an underlying Fact. So, some convenience methods are there to make life easier when working with when() and then() methods.

getOne() gets the value of the Fact when only one Fact exists in the FactMap

getValue(String name) gets the value of the Fact by the name of the Fact

setValue(String name, T value) sets the Fact with the name specified to the new value

put(Fact fact) adds a Fact to the FactMap, using the Fact's name as the key for the Map

toString() toString gets the toString() method of the Fact's value when only one Fact exists

The following methods are part of the NameValueReferrableTypeConvertible interface, which is implemented by the TypeConvertibleFactMap class as a NameValueReferrable decorator. You can think of it as a decorator for FactMaps (because it's also that too!) and it's what's used to inject facts into when() and then() methods.

getStrVal(String name) gets the value of the Fact by name as a String

getDblVal(String) gets the value of the Fact by name as a Double

getIntVal(String) gets the value of the Fact by name as an Integer

getBigDeciVal(String) gets the value of the Fact by name as a BigDecimal

getBoolVal(String) gets the value of the Fact by name as a Boolean

3.5 Auditing Rules

Rules auditing can be enabled when constructing a RuleBook by specifying asAuditor() as follows.

 RuleBook rulebook = RuleBookBuilder.create().asAuditor()
   .addRule(rule -> rule.withName("Rule1").withNoSpecifiedFactType()
     .when(facts -> true)
     .then(facts -> { } ))
   .addRule(rule -> rule.withName("Rule2").withNoSpecifiedFactType()
     .when(facts -> false)
     .then(facts -> { } )).build();
     
 rulebook.run(new FactMap());

By using asAuditor() each rule in the RuleBook can register itself as an Auditable Rule if its name is specified. Each Auditable Rule added to a RuleBook Auditor has its state recorded in the RuleBook. At the time when rules are registered as auditable in the RuleBook, their RuleStatus is NONE. After the RuleBook is run, their RuleStatus is changed to SKIPPED for all rules that fail or whose conditions do not evaluate to true. For rules whose conditions do evaluate to true and whose then() action completes successfully, their RuleStatus is changed to EXECUTED.

Retrieving the status of a rule can be done as follows.

 Auditor auditor = (Auditor)rulebook;
 System.out.println(auditor.getRuleStatus("Rule1")); //prints EXECUTED
 System.out.println(auditor.getRuleStatus("Rule2")); //prints SKIPPED

A map of all rule names and their corresponding status can be retrieved as follows.

 Map<String, RuleStatus> auditMap = auditor.getRuleStatusMap();

3.6 Rule Chain Behavior

By default, errors found when loading rules or exceptions thrown when running rules, remove those rules from the rule chain. In other words, rules that error are just skipped. Additionally, by default, a rule can only stop the rule chain if its condition evaluates to true and if its actions successfully complete.

However, this behavior can be changed on a per-rule basis.

RuleBook ruleBook = RuleBookBuilder.create()
    .addRule(
        RuleBuilder.create(GoldenRule.class, RuleChainActionType.STOP_ON_FAILURE)
            .withFactType(String.class)
            .when(facts -> true)
            .then(consumer)
            .stop()
            .build())
    .addRule(
        RuleBuilder.create()
            .withFactType(String.class)
            .when(facts -> true)
            .then(consumer)
            .build())
    .build();

In the above example, the default RuleChainActionType.CONTINUE_ON_FAILURE is changed to RuleChainActionType.STOP_ON_FAILURE in the first rule. This will ensure that if there is an error in the first rule, the 2nd rule will never be invoked. However, no error will be thrown.

If the desired behavior was to throw any exception that occurred in the first rule and stop the rule chain, the following code could be used.

RuleBook ruleBook = RuleBookBuilder.create()
    .addRule(
        RuleBuilder.create(GoldenRule.class, RuleChainActionType.ERROR_ON_FAILURE)
            .withFactType(String.class)
            .when(facts -> true)
            .then(consumer)
            .build())
    .addRule(
        RuleBuilder.create()
            .withFactType(String.class)
            .when(facts -> true)
            .then(consumer)
            .build())
    .build();

3.6.1 Rule Chain Action Types Defined

RuleChainActionType Description
CONTINUE_ON_FAILURE the default RuleChainActionType; false rule conditions and errors effectively 'skip' the rule
ERROR_ON_FAILURE exceptions thrown by rules stop the rule chain and bubble up the exception as a RuleException
STOP_ON_FAILURE rules that have their RuleState set to BREAK will stop the RuleChain if the rule's condition is false or if an exception is thrown

Top

4 POJO Rules

As of RuleBook v0.2, POJO rules are supported. Simply define your rules as annotated POJOs in a package and then use RuleBookRunner to scan the package for rules and create a RuleBook out of them. It's that simple!

4.1 A Hello World Example

package com.example.rulebook.helloworld;

import com.deliveredtechnologies.rulebook.annotations.*;
import com.deliveredtechnologies.rulebook.RuleState;

@Rule
public class HelloWorld {

  @Given("hello")
  private String hello;

  @Given("world")
  private String world;

  @Result
  private String helloworld;

  @When
  public boolean when() {
      return true;
  }

  @Then
  public RuleState then() {
      helloworld = hello + " " + world;
      return RuleState.BREAK;
  }
}
public static void main(String args[]) {
  RuleBookRunner ruleBook = new RuleBookRunner("com.example.rulebook.helloworld");
  NameValueReferableMap facts = new FactMap();
  facts.setValue("hello", "Hello");
  facts.setValue("world", "World");
  ruleBook.run(facts);
  ruleBook.getResult().ifPresent(System.out::println); //prints "Hello World"
}

4.2 A New MegaBank Example With POJO Rules

MegaBank changed their rate adjustment policy. They also now accept loan applications that include up to 3 applicants. If all of the applicants credit scores are below 600, then they must pay 4x the current rate. However, if all of the applicants have a credit score of less than 700, but at least one applicant has a credit score greater than 600, then they must pay an additional point on top the rate. Also, if any of the applicants have a credit score of 700 or more and the sum of the cash on hand available from all applicants is greater than or equal to $50,000, then they get a quarter point reduction in their rate. And if at least one applicant is a first time home buyer and at least one applicant has a credit score of over 600, then they get a 20% reduction in their calculated rate after all other adjustments are made.

...using the ApplicantBean defined above

@Rule(order = 1) //order specifies the order the rule should execute in; if not specified, any order may be used
public class ApplicantNumberRule {
  @Given
  private List<ApplicantBean> applicants; //Annotated Lists get injected with all Facts of the declared generic type

  @When
  public boolean when() {
    return applicants.size() > 3;
  }

  @Then
  public RuleState then() {
    return RuleState.BREAK;
  }
}
@Rule(order = 2)
public class LowCreditScoreRule {
  @Given
  private List<ApplicantBean> applicants;

  @Result
  private double rate;

  @When
  public boolean when() {
    return applicants.stream()
      .allMatch(applicant -> applicant.getCreditScore() < 600);
  }

  @Then
  public RuleState then() {
    rate *= 4;
    return BREAK;
  }
}
@Rule(order = 3)
public class QuarterPointReductionRule {
  @Given
  List<ApplicantBean> applicants;

  @Result
  private double rate;

  @When
  public boolean when() {
    return
      applicants.stream().anyMatch(applicant -> applicant.getCreditScore() >= 700) &&
      applicants.stream().map(applicant -> applicant.getCashOnHand()).reduce(0.0, Double::sum) >= 50000;
  }

  @Then
  public void then() {
    rate = rate - (rate * 0.25);
  }
}
@Rule(order = 3)
public class ExtraPointRule {
  @Given
  List<ApplicantBean> applicants;

  @Result
  private double rate;

  @When
  public boolean when() {
    return
      applicants.stream().anyMatch(applicant -> applicant.getCreditScore() < 700 && applicant.getCreditScore() >= 600);
  }

  @Then
  public void then() {
    rate += 1;
  }
}
@Rule(order = 4)
public class FirstTimeHomeBuyerRule {
  @Given
  List<ApplicantBean> applicants;

  @Result
  private double rate;

  @When
  public boolean when() {
    return
      applicants.stream().anyMatch(applicant -> applicant.isFirstTimeHomeBuyer());
  }

  @Then
  public void then() {
    rate = rate - (rate * 0.20);
  }
}
public class ExampleSolution {
  public static void main(String[] args) {
    RuleBookRunner ruleBook = new RuleBookRunner("com.example.rulebook.megabank");
    NameValueReferableMap<ApplicantBean> facts = new FactMap<>();
    ApplicantBean applicant1 = new ApplicantBean(650, 20000, true);
    ApplicantBean applicant2 = new ApplicantBean(620, 30000, true);
    facts.put(new Fact<>(applicant1));
    facts.put(new Fact<>(applicant2));

    ruleBook.setDefaultResult(4.5);
    ruleBook.run(facts);
    ruleBook.getResult().ifPresent(result -> System.out.println("Applicant qualified for the following rate: " + result));
  }
}

4.3 POJO Rules Explained

POJO Rules are annotated with @Rule at the class level. This lets the RuleBookRunner know that the class you defined is really a Rule. Facts are injected into POJO Rules using @Given annotations. The value passed into the @Given annotation is the name of the Fact given to the RuleBookRunner. The types annotated by @Given can either be the generic type of the matching Fact or the Fact type as seen above. The big difference between the two is that changes applied to immutable objects are not propagated down the rule chain if the Fact’s generic object is changed (because it would then be a new object). However, if you set the value on a Fact object, those changes will be persisted down the rule chain.

The @When annotation denotes the method that is used as the condition for executing the ‘then’ action. The method annotated with @When should accept no arguments and it should return a boolean result.

The @Then annotation denotes the action(s) of the rule that is executed if the ‘when’ condition evaluates to true. The method(s) annotated with @Then should accept no arugments and it can optionally return a RuleState result. If more than one method in a POJO rule is annotated with @Then, then all rules annotated with @Then are executed if the 'when' condition evaluates to true.

The @Result annotation denotes the result of the Rule. Of course, some Rules may not have a result. In that case, just don’t use the @Result annotation. It’s that simple.

4.3.1 Ordering POJO Rules

The ‘order’ property can [optionally] be used with the @Rule annoataion to specify the order in which POJO Rules will execute as seen above. If the order property is not specified, then Rules may execute in any order. Similarly, more than one Rule may have the same order, which would mean that the Rules with a matching order can fire in any order - order would then denote a group of rules, where the execution of the group is ordered among other rules, but the execution of the rules within that group doesn’t matter.

4.3.2 Injecting Collections into POJO Rules

If the following conditions are met then the objects contained in all Facts of generic type specified are injected into a collection:

  • A List, Set, Map or FactMap is annotated with a @Given annotation
  • The @Given annotation on the collection has no value specified
  • The generic type of the List, Set, Map (the first generic type in a Map is String - representing the name of the Fact injected) or FactMap is the same type of at least one Fact supplied to the RuleBookRunner

4.3.3 POJO Rule Annotation Inheritance

As of v.0.3.2, RuleBook supports annotation inheritance on POJO Rules. That means if you have a subclass, whose parent is annotated with RuleBook annotations (i.e. @Given, @When, @Then, @Result) then the subclass will inherit the parent’s annotations. @Given and @Result attributes injected in the parent, will be available to the subclass. @Then and @When methods defined in the parent will be visible in the subclass.

4.3.4 Auditing POJO Rules

Auditing is built into POJO Rules via the RuleBookRunner and each POJO Rule is automatically audited. If a name is specified in the @Rule attribute, then that name is used for auditing. Otherwise, the class name of the POJO rule is used. For example, assuming that there is a POJO rule named "My Rule" that was run by the RuleBookRunner, the status of that rule execution can be retrieved as follows.

 Auditor auditor = (Auditor)rulebookRunner;
 RuleStatus myRuleStatus = auditor.getRuleStatus("My Rule");

4.3.5 POJO Rule Chain Behavior

@Rule(ruleChainAction = ERROR_ON_FAILURE)
public class ErrorRule {
  @When
  public boolean when() {
    return true;
  }

  @Then
  public void then() throws Exception {
    throw new CustomException("Sumthin' Broke!");
  }
}

As seen in the example directly above, the ruleChainAction Rule parameter can be use to change the rule chain behavior for specific rules as detailed in 3.6 Rule Chain Behavior.

[Top]

5 Using RuleBook with Spring

RuleBook can be integrated with Spring to inject instances of RuleBooks that are created from POJOs in a package. RuleBooks can be specified using either the Java DSL or POJO Rules. And since RuleBooks are threadsafe, they can be used as Singeltons, Spring's default for injecting beans. Additionally, POJO Rules can now be made Spring Aware, so you can inject Spring components using @Autowire.

5.1 Adding RuleBook Spring Support to Your Project

The preferred way to use RuleBook with Spring is to configure a SpringAwareRuleBookRunner. Then, simply add the @RuleBean annotation to any POJO Rules that you would like to work with Spring. If you omit the @RuleBean annotation then the @POJO Rule(s) without @RuleBean can still be loaded and run, they just will not be managed by or scoped properly for Spring and @Autowired will not work within the Rule.

5.2 Creating Spring Enabled POJO Rules

POJO Rules can be created just like they were created above without Spring, but with some extra Spring goodness! To create Spring enabled POJO Rules, first add rulebook-spring as a dependency.

Maven:

<dependency>
    <groupId>com.deliveredtechnologies</groupId>
    <artifactId>rulebook-spring</artifactId>
    <version>0.12</version>
</dependency>

Gradle:

compile 'com.deliveredtechnologies:rulebook-spring:0.12'

Note: 0.11 is currently the only version of rulebook-spring that provides SpringAwareRuleBookRunner, which is what allows Rules to have injected @Autowired Spring components.

The trivial example below demonstates the basic functionality.

package com.exampl.rulebook.helloworld.component;

@Component
public class HelloWorldComponent {
  public String getHelloWorld(String hello, String world) {
    return hello + " " + world + "!";
  }
}
package com.example.rulebook.helloworld;

@RuleBean
@Rule(order = 1)
public class HelloSpringRule {
  @Given("hello")
  private String hello;

  @Result
  private String result;

  @When
  public boolean when() {
    return hello != null;
  }

  @Then
  public void then() {
    result = hello;
  }
}
package com.example.rulebook.helloworld;

@RuleBean
@Rule(order = 2)
public class WorldSpringRule {
  @Autowired
  HelloWorldComponent helloWorldComponent;
  
  @Given("world")
  private String world;

  @Result
  private String result;

  @When
  public boolean when() {
    return world != null;
  }

  @Then
  public void then() {
    result = helloWorldComponent.getHelloWorld(result, world);
  }
}

5.3 Configuring a RuleBook in Spring

@Configuration
@ComponentScan("com.example.rulebook.helloworld")
public class SpringConfig {
  @Bean
  public RuleBook ruleBook() {
    RuleBook ruleBook = new SpringAwareRuleBookRunner("com.example.rulebook.helloworld");
    return ruleBook;
  }
}

5.4 Using a Spring Enabled RuleBook

  @Autowired
  private RuleBook ruleBook;

  public void printResult() {
    NameValueReferableMap<String> facts = new FactMap<>();
    facts.setValue("hello", "Hello ");
    facts.setValue("world", "World");
    ruleBook.run(facts);
    ruleBook.getResult().ifPresent(System.out::println); //prints Hello World!
  }

5.5 Ordering Rules With Spring

If you were using the RuleBean annotation to create Spring enabled Rules, all of that stuff still works. And there Spring enabled POJO Rules can still be configured in RuleBooks in Spring [using SpringRuleBook]. But RuleBean doesn't have an order property. So, if you need to order beans scanned using a RuleBookFactoryBean, just use the @Rule annotation like you would with regular non-Spring enabled POJO Rules. It works exactly the same way!

[Top]

6 How to Contribute

Suggestions and code contributions are welcome! Please see the Developer Guidelines below.

6.1 Developer Guidelines

Contributions must adhere to the following criteria:

  1. The forked repository must be publicly visible.
  2. The issues addressed in the request must be associated to an accepted issue.
  3. The build (i.e. ./gradlew build) must pass with no errors or warnings.
  4. All new and existing tests must pass.
  5. The code must adhere to the style guidleines icluded in the checkstyle configuration (i.e. no checkstyle errors).
  6. Newly introduced code must have at least 85% test coverage.
  7. Pull requests must be for the develop branch.
  8. The version number in gradle.properties should match the milestone of the issue its associated with appended with -SNAPSHOT (ex. 0.2-SNAPSHOT)

Anyone may submit an issue, which can be either an enhancement/feature request or a bug to be remediated. If a feature request or a bug is approved (most reasonable ones will be), completed and an associated pull request is submitted that adheres to the above criteria, then the pull request will be merged and the contributor will be added to the list of contributors in the following release.

[Top]

rulebook's People

Contributors

awattez avatar azell avatar bradegler avatar briandeacon avatar clayton7510 avatar harshavs avatar jabhijeet avatar jimmymain avatar mikelear avatar mill5james avatar nicolaslambert avatar rizjoj avatar tomekbielaszewski avatar vidi42 avatar xplutox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rulebook's Issues

Is it possible to stop RuleBook evaluations when exceptions are thrown?

Hi. This is an awesome library!

Suppose the following:

  • I have a service call with side effects that can throw checked or unchecked exceptions at runtime (e.g. a database read/write call).
  • The method call is embedded as a then method on a rule evaluation.
  • If the service method fails (i.e. throws an Exception), the rule book evaluation continues to execute.

In my use case, is it possible to configure RuleBook or each individual rule in such a way that any unhandled Exceptions stop the rule book evaluation and have the Exception bubble up back to the caller? Is there a better way to handle this scenario?

Thanks again!

Re-using RuleBook instance

Hi Clayton, I have a question. Is it possible to re-use a RuleBook instance? So, is it possible to call run method in a cycle for example ? Thanks.

Thread safety?

Is RuleBook thread safe for multithreading environment?
Should I create an instance for each thread for rule evaluation?

Springify RuleBook

RuleBook can already be used easily with Spring. But there are a few things the developer has to be aware of: the Rules, Decisions and Books must be prototypes because they persist state which may be interrupted and changed if they were singletons.

There might be some other useful stuff that can be streamlined for Spring support, too.

RuleBook first run unexpected result

Hi,

I'm trying to integrate RuleBook in an enterprise project, because it seems to be really interesting.

I'm using version 0.5 and found that first run returns a default value because headRule is not correctly valued in CoRRuleBook.run method. These are the steps I did:

  1. First I created a new RuleBook extending CoRRuleBook:
public class AutoEvaluationRuleBook extends CoRRuleBook<MyBean> {

	@Override
	public void defineRules() {
           // here I define all my business rules
	}

}
  1. Then I instantiate a new RuleBook:
RuleBook<AutoEvaluationResult> autoEvaluationRuleBook = RuleBookBuilder.create(AutoEvaluationRuleBook.class)
				.withResultType(AutoEvaluationResult.class)
				.withDefaultResult(new AutoEvaluationResult(null, false, false, null))
				.build();
  1. Then I create some test cases:
List<NameValueReferableMap<MyBean>> factsList = testCases();

factsList.forEach(x -> {
	count++;
	autoEvaluationRuleBook.run(x);
	autoEvaluationRuleBook.getResult().ifPresent(result -> {
	MyBean ref = result.getValue().getRef();
	System.out.println(String.format("%s. Item: %s, Autoevaluated: %s, Suspect: %s, Reason: %s", count,
		null != ref ? "item" : "NULL", result.getValue().isAutoEvaluated(),
		result.getValue().isSuspect(), null != result.getValue().getReason() ? result.getValue().getReason().name() : "NULL"));
	});
});

In this way, the first run returns an unexpected result because I noticed in the run method inside the CoRRuleBook class the object headRule is not valued.

The original code:

  @Override
  @SuppressWarnings("unchecked")
  public void run(NameValueReferableMap facts) {
   // here an Optional obj wraps the class property _headRule
    Optional<Handler<Rule>> headRule = Optional.ofNullable(_headRule);
    if (!headRule.isPresent()) {
      // if not present (first run), you call defineRules()
      defineRules();
    }
   // here headRule Optional value is still null, is it wanted ?
    headRule.ifPresent(ruleHandler -> {
        ruleHandler.getDelegate().setFacts(facts);
        getResult().ifPresent(result -> ruleHandler.getDelegate().setResult(result));
      });
    headRule.ifPresent(Handler::handleRequest);
  }

In the original code if the headRule is not present, it calls the defineRules, defining the book rules, but after that the method isPresent is called on the Optional<Handler> headRule object which will return false. Is this wanted ?

I modified this code in this way:

@Override
@SuppressWarnings("unchecked")
public void run(NameValueReferableMap facts) {
	Optional<Handler<Rule>> headRule = Optional.ofNullable(_headRule);
	if (!headRule.isPresent()) {
		defineRules();
                // new added line below, rules are defined but object is still not present
		headRule = Optional.ofNullable(_headRule);
	}		
	headRule.ifPresent(ruleHandler -> {
		ruleHandler.getDelegate().setFacts(facts);
		getResult().ifPresent(result -> ruleHandler.getDelegate().setResult(result));
	});
	headRule.ifPresent(Handler::handleRequest);
}

In this way I have all the objects valued correctly (also the first one).

Let me know.

Thanks and best regards,
Alessandro Torrisi.

Different Fact types in one FactMap

Hello again.

Is it possible to include multiple object types in one FactMap, after setting the FactType in the ruleBuilder? Example:

I'm first building the rule with a FactType of Deduction.class:

private RuleBuilder<Deduction,Boolean> getDeductionRuleBuilder(){ return RuleBuilder.create().withFactType(Deduction.class).withResultType(Boolean.class); }

When I setup my FactMap, I have a Deduction and String object I want to pass in:
private NameValueReferableMap getFactMap(Deduction deduction, String state) { NameValueReferableMap facts = new FactMap<>(); facts.setValue("deduction", deduction); facts.setValue("state", state); return facts; }

Here's an example rule I have defined:
addRule(getDeductionRuleBuilder() .when(facts -> facts.getOne().getType().equals(SOME_EQUALITY_CHECK)) .then((factMap, booleanResult) -> { System.out.println(factMap.getStrVal("state")); booleanResult.setValue(Constants.SOME_OTHER_CHECK.contains(factMap.getStrVal("state"))); }).stop().build());
So, when I execute the Rule, obviously the Deduction fact is retained, however the String object seems to get cut out of the factmap, and it returns false because it can't evaluate the "state" Fact. I realize I could easily create a container object to hold the Deduction and String object. However, I wanted to make sure there was no other way to create a FactMap of various object types to be used in a RuleBook.

Make it easier to use the DSL with Facts of different types in a RuleBook

If you are using POJO Rules, this is a non-issue. But if you are using the Java DSL, then mixing types of Facts is cumbersome since generics suppose all Facts are of the same type.

This should be an easier thing to do. I'm thinking that a RuleBook can be of an unspecified type and then Rules of any type can be added. Facts of all types would be chained across Rules. But only Facts of the type of the Rule would be available in the When and Predicate, still keeping the lambda syntax pretty streamlined.

Is it possible to have an "else" for a rule?

Hi, is there some way to have like an "else" for a rule? I have to run a set of rules, but I need to know which one "passed" and which ones not, so I was thinking about having a Result consisting on a list of "states" for each rule ran.
The problem is, I have no access to the "result" in the "when" rule part, and the "then" is never ran if the rule result is false. Is there any way to achieve this with Rulebook? Thanks in advance!.

Rules cannot iterate through facts?

I understand one can apply multiple rules to any value on the facts map.

But what if I have a List of objects that I want to have evaluated by the same rule pipeline?

Imagine I have something like:

List<Person> people = new ArrayList<Person>();
people.add(new Person("Steve", 30));
people.add(new Person("Eddie", 42));
people.add(new Person("Mary", 32));

RuleBook ruleBook = ruleBookBuilder.addRule(RuleBuilder.create().withFactType(JsonObject.class)
                .when(person -> person.age < 40)
                .then(person -> person.getName())
                .build()).build();

        ruleBook.run(people);

-------OUTPUT------

Steve
Mary

It seems that if I want to put a List/Set of objects through a rule pipeline at the moment I always need to recreate a new Rulebook, is this right?

Externalized Rules Using a BDSL

I'm thinking that it would be cool to be able to have rules stored outside of the application (and the JVM) and have RuleBook be able to point to those rules and use them.

Supported languages and format is currently TBD. Maybe Groovy?

Make POJO Rules Stateless

Thread safety in POJO RuleBooks requires different instances of RuleBooks per thread since state is stored in the Rules. This allows the interface to very intuitive. But unfortunately, it also means that each thread needs its own POJO RuleBook. I think the solution is to have Facts injected as parameters instead of instance variables.

More Sugar

There are a couple of things in the new DSL that are a bit cumbersome. Retrieving results is one of those things. Maybe it could be cleaned up a bit.

Rules Auditing

It comes to mind that it might be useful to have an audit of rules available. Say, for example, I have a RuleBook that contains many different rules. When I run that RuleBook, some rules might fire, some rules might not. It might be nice to know which rules executed.

I'm thinking rules could be audited by name. That means, each rule should have a name. By default, it could be the classname of the Rule. But if a String name is specified, then it would be the name specified.

POJO Rules don't support injection of multiple facts based on the type

The MegaBank example won't work correctly with POJOs since POJOs cannot currently be injected with a grouping of objects stored in different facts. Right now, it only injects based on the name. However, in the MegaBank example, the name might not be known ahead of time, nor would the number of Facts.

This should probably be fixed before the next minor release.

Multiple invocations of RuleBook.run() could define rules more than once

Practically, this shouldn't be a huge issue since RuleBooks are likely instantiated each time they are to be used (Spring does this with prototype) and therefore run() would likely only be called once per RuleBook instance. However, if a developer wanted to create a single instantiation of a RuleBook class and re-run it then there could be an issue with rules running multiple times unintentionally. This should be corrected in a minor release.

stop() in POJO-Rules with Spring

When using POJO-Rules with Spring, is there any way to configure a Rule as last Rule, like in the DSL-examples: addRule(RuleBuilder.create().....stop().build());? Im looking for a way to create a rulebook with different rules that can be executed exclusively (like "only execute the first matching rule"), so i could have a default rule with low order to be executed if there has been no more specific matching rule before.

Don't know if i just haven't found the correct way or there's no way to do this at the moment. And if there's no way to do this at the moment, would it make sense to add this feature or is this a strange use case?

Megabank example - bug or feature?

As I understand it, the ApplicantNumberRule only allows loan applications with 3 or fewer applicants to be processed, i.e. if greater than three applicants then processing is halted. In my main method I supplied 4 applicants:

 RuleBookRunner ruleBook = new RuleBookRunner("rules");
    NameValueReferableMap<ApplicantBean> facts = new FactMap<ApplicantBean>();
    ApplicantBean applicant1 = new ApplicantBean(650, 20000, true);
    ApplicantBean applicant2 = new ApplicantBean(620, 30000, true);
       ApplicantBean applicant3 = new ApplicantBean(620, 30000, true);//same as applicant2
       ApplicantBean applicant4 = new ApplicantBean(900, 30000, true);
       facts.put(new Fact<>(applicant1));
       facts.put(new Fact<>(applicant2));
       facts.put(new Fact<>(applicant3));
       facts.put(new Fact<>(applicant4));
    
       ruleBook.setDefaultResult(4.5);
       ruleBook.run(facts);
       ruleBook.getResult().ifPresent(result -> System.out.println("Applicant qualified for the following rate: " + result));

output:

  Applicants count:3
  Applicant qualified for the following rate: 3.3

Because two of the applicants have the same facts.
If I change one of the repeated applicants so that all the applicants are unique the rule fires as expected (4 applicants).

Is this a bug or a feature?
P.S. also the code copied and pasted from the readme did not compile with some missing ")"s

Unexpected results in 4.1 A Hello World Example?

hellowordrule.txt
example4.1.txt
Hi Clayton,

Running the Hello World example using bogus facts the rulebook.getResults().isPresent() is true. Should the results be false? I was expecting a Null value.

Running the Hello World example using RuleBookBuilder using bogus facts the rulebook.getResults().isPresent() is false. Which is what I expected.

Love the RuleBook. Keep up the good work.

cheers,
Kent

Additional Algorithms for Rules Evaluation

Rules Engine uses CoR for sequential rules evaluation. It works! And for many situations, it's serves the need. However, for more ambitious applications, it would be nice to have other algorithms for rules evaluation available. The catch is that other algorithms should have a sequential dependency component to provide predictable execution between dependent rules. In addition, rules evaluation algorithms should be pluggable - the library certainly allows for it.

This is not pressing. On the 1. Make it work 2. Make it good 3. Make it fast hierarchy, it's definitely in the 3rd category. And there are other things (like versioning and BDSL) that should take precedence. But this is something that will be needed at some point down the line.

Rules Versioning

Rules should be able to be versioned independently of the application code. When a new version of rules is published, the application should load the newly published rules and start running the new rules version in place of the old one.

A couple of things to consider:

  • in-flight rules should not break during a rules version update
  • version updates should have various modes of scheduling (it could use quartz or EJB timer, perhaps)

Initially, repositories for rules could be Maven. Maybe later, Git could be supported.

Run several rules each returning a result

Hi,

I have several rules, each having to be triggered (when is true) and so each one should return the name of the winner (some String value). I have been wondering if you have an advice to give me to achieve that. I thought about creating a RuleBook instance per rule, but this is quite tedious and messy. Also, I am using Spring and the Rule logic is quite long, so either I'd have to create a package per Rule (quite messy!) or I'd have to use addRule() on each Rule instance and instanciate them manually.

Thanks!

Valentin

Stopping the chain if a Rule returns false

Hi

If I want to add a bunch of rules, where I want the result to contain success ONLY if all rules are successful, is this possible currently. So, lets say that I have two rules, the first one checks if a user is Male, the second checks whether the users age is less than 40. If the user happens to be female, then the first rule will return false, but the second rule will still process. If the second rule returns true (the user is less than 40), then the rulebook result is true, even though I actually want it to be false. Essentially I'd like to be able to stop the chain should a rule fail, but this doesn't look possible. Is there another way of doing this possibly ?

Thanks

Darrell

More Robust Application Examples

I think the time has come to include complete working project examples in the GitHub source. Suggestions are welcome. Contributions are encouraged.

When condition seems not working. A very easy question regarding with going through the example on the toturial

Hi, I tried to do the simple Hello World example to see how the library generally work, but the result is a little bit funky.
Here is the code I wrote:
import com.deliveredtechnologies.rulebook.FactMap;
import com.deliveredtechnologies.rulebook.NameValueReferableMap;
import com.deliveredtechnologies.rulebook.Result;
import com.deliveredtechnologies.rulebook.lang.RuleBookBuilder;
import com.deliveredtechnologies.rulebook.model.RuleBook;
import com.deliveredtechnologies.rulebook.lang.RuleBuilder;
import com.deliveredtechnologies.rulebook.Rule;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.lang.String;

class ruletest{
public static void main(String [] arg){
NameValueReferableMap factMap = new FactMap();
factMap.setValue("hello", "Hello");
factMap.setValue("world", " World");
RuleBook ruleBook = RuleBookBuilder.create().withResultType(Integer.class)
.addRule(rule -> rule.withFactType(FactMap.class)
.when(f -> f.containsKey("hello") )
.using("hello").then(t -> System.out.println(1) ))
.addRule(rule -> rule.withFactType(FactMap.class)
.when(f -> !f.containsKey("hello_2"))
.then(t -> System.out.println(2) ))
.build();

    ruleBook.run(factMap); //return 2. However it should be 1
    System.out.println(factMap.containsKey("hello")); //Return true
}

}

When I run the code, it seems that it is not working. the expected result should be 1 instead of 2.
Thank you for the help.
Wish you have a good day

HomeLoanRateRuleBook example not working

Hello,

Was trying to run through your HomeLoan example and it doesn't appear to be returning any result. I have the ApplicantBean and the Main classes copied verbatim. However instantiating the HomeLoanRateRuleBook object gives me a compile error because the following is incompatible:

HomeLoanRateRuleBook homeLoanRateRuleBook = RuleBookBuilder.create(HomeLoanRateRuleBook).withResultType(Double.class) .build();

Results in "Incompatible types". Required HomeLoanRateRuleBook, found RuleBook

If I try casting as HomeLoanRateRuleBook or even setting the variable as:

RuleBook<Double> homeLoanRateRuleBook = RuleBookBuilder.create(HomeLoanRateRuleBook).withResultType(Double.class) .build();

It does not work. Any time I run through the steps the RuleBook result (_result) is coming back null. Only when I specify a defaultResult does it return a value back. Any ideas?

Verify execution order with POJO rules

Execution order was included in POJO rules, but it was not verified. It should be verified to ensure that facts and/or results are used & modified in the order specified.

FileSystemNotFoundException on standalone Spring Boot app

I like where this library is going so I am giving it a shot. The main problem I've encountered that I don't seem to find a workaround for is this FileSystemNotFoundException that occurs when I run the RuleBook standalone (I am using the annotated classes not the Java DSL). Everything works fine in IntelliJ but if I try to just java -jar target/long-jar-name.jar from the command line it fails. I am running this in a standard Spring Boot app.

It appears that the problem could be how the Paths are created using NIO: https://stackoverflow.com/questions/22605666/java-access-files-in-jar-causes-java-nio-file-filesystemnotfoundexception

I am using version 0.8 pulled from Maven Central. Here is what the exception looks like:

2017-09-12 23:18:22 [http-nio-8080-exec-3] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.nio.file.FileSystemNotFoundException] with root cause
java.nio.file.FileSystemNotFoundException: null
        at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
        at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
        at java.nio.file.Paths.get(Paths.java:143)
        at com.deliveredtechnologies.rulebook.model.runner.RuleBookRunner.findRuleClassesInPackage(RuleBookRunner.java:120)
        at com.deliveredtechnologies.rulebook.model.runner.RuleBookRunner.run(RuleBookRunner.java:68)

Passing a Collection (Map,List) as Given shows up as null in rule?

You can tell me if I'm just not using it correctly, but --

If I pass a single POJO as Given, I retrieve the List in my rule and process it as normal, as your documentation states. However, trying to pass a collection seems to result in the object either being swallowed up and spit out as null, or as an empty List. I can provide an example if needed.

addRule() method for RuleBookFactoryBean

Is there any way we can get the addRule functionality added to the RulebookFactoryBean? Had a setup like below where we created rulebookbeans (at prototype scope) and were able to use the same rules from the same package within different rulebooks. After updating the version I noticed the RuleBookBean is deprecated and you now only have package scanning available for spring created rulebooks.

`@Configuration
public class RulesEngines {

@bean(name="defaultRule")
@scope("prototype")
public RuleBookBean inRuleBook(FilterItemRateMapRule filterItemRateMapRule, InterstateRule interstateRule, IntrastateRule intrastateRule, CountryRule countryRule) throws InvalidClassException {
return getRuleBook(filterItemRateMapRule, interstateRule, intrastateRule,countryRule);
}

@bean(name="indiaRuleMTR")
@scope("prototype")
public RuleBookBean inRuleBookMTR(FilterItemRateMapRule filterItemRateMapRule, InterstateRule interstateRule, IntrastateRuleMTR intrastateRule,CountryRule countryRule) throws InvalidClassException {
return getRuleBook(filterItemRateMapRule, interstateRule, intrastateRule,countryRule);
}

private RuleBookBean getRuleBook(FilterItemRateMapRule filterItemRateMapRule, InterstateRule interstateRule, ApplicableTaxRule intrastateRule, CountryRule countryRule) throws InvalidClassException {

RuleBookBean inRuleBook = new RuleBookBean();
inRuleBook.addRule(filterItemRateMapRule);
inRuleBook.addRule(countryRule);
inRuleBook.addRule(interstateRule);
inRuleBook.addRule(intrastateRule);
return inRuleBook;

}

}
`

Add Some More Syntactic Sugar to the DSL

  • Add a Consumer then() method option that continues by default.
  • Add the ability to chain multiple then() statements.
  • Add a stop() method that breaks on then()
  • Add a given() method that accepts a String and an Object of some type T for adding a single Fact
    • Do this for Rules and RuleBooks

Using method not working as expected

In the below example where using is used(highlighted in bold):

RuleBook ruleBook = RuleBookBuilder.create().withResultType(String.class)
.withDefaultResult("Unknown!")
.addRule(rule->rule.withFactType(String.class)
.when(f->f.containsKey("hello") && f.containsKey("world"))
.using("hello").then(System.out::print)
.using("world").then(System.out::println))

.build();

The outcome is actually object reference like:
com.deliveredtechnologies.rulebook.TypeConvertibleFactMap@5f4da5c3com.deliveredtechnologies.rulebook.TypeConvertibleFactMap@443b7951

Instead of "Hello World"

Facts of different actual types

Is it possible to use a Java interface and/or a general class (instead of the concrete type) as the generic type passed to Facts and FactMap and the @given List? I replaced references to ApplicantBean with an interface (IApplicant which declares the expected methods) e.g. FactMap and in the rule:
@given private List applicants
I end up with no objects in the applicants list. I've tried this with a generalized class as well (where concrete classes are specializations of ApplicantBean).

MegaBank Example With POJO Rules not working

I don't think the MegaBank Example With POJO Rules are documented properly and even its working ?

I tried to simulated MegaBank Example With POJO Rules but the rules are not getting invoked.
Result is simply coming null.
Applicant qualified for the following rate: null null

RuleState vs RuleChainActionType

Can you explain what the expected behavior would be for setting RuleState to next/break vs setting the RuleChainActionType to continue/stop on failure? I noticed setting STOP_ON_FAILURE automatically sets the rule to RuleState.NEXT. Just looking for some clarity on these values.

When-condition of rule is not accessed

Hi,

I'm trying to use RuleBook version 0.5 in my Spring-application. It seems like the when-part will never be evaluated, so I access for each rule the then-part.
First there comes my code, then the issue is explained with a little bit more details.

The Code:

My Pom contains:

<dependency>
    <groupId>com.deliveredtechnologies</groupId>
    <artifactId>rulebook-core</artifactId>
    <version>0.5</version>
</dependency>
<dependency>
    <groupId>com.deliveredtechnologies</groupId>
    <artifactId>rulebook-spring</artifactId>
    <version>0.5</version>
</dependency>

Like in the wiki/readme told I created a @Configuration-bean:

@Configuration
public class RuleBookSpringConfig {
  @Bean
  public RuleBookFactoryBean ruleBook() throws InvalidClassException {
	return new RuleBookFactoryBean("my.package.rulebook.myRules"); 
  }
}

I defined one rule in the package:

@Rule(order =1)
public class MyRule {
  public MyRule() {}

  @Given("field")
  private myClass field;
  
  @When
  public boolean when() {
	System.out.println("Evaluate condition");
	return field.isPropertySet(); 
  }
  
  @Then
  public RuleState then() {
	System.out.println("Rule is correct");
	return RuleState.NEXT;
  }
}

This rule is used here:

NameValueReferableMap<FieldMetaElement> facts = new FactMap<>();
facts.setValue("field", instanceOfMyClass);
this.ruleBook.run(facts);

The issue:

If I run the code, my output is every time Rule is correct, wether the property is set or not. The output Evaluate condition is never displayed. So I changed the when-part to:

@When
public boolean when() {
    return false;
}

The output is every time Rule is correct again. If I set a breakpoint to when() it does not stop there. So the when-part is not executed at every time.
If I set a breakpoint at com.deliveredtechnologies.rulebook.model.runner.RuleAdapter.class line 126 - public Predicate<NameValueReferableMap> getCondition() the application does not stop there. Same at com.deliveredtechnologies.rulebook.runner.RuleAdapter.class line 121 - public Predicate getWhen().
I tried to use a manually instance of RuleBook without spring and have exactly the same situation.
Same situation comes if I use a rule with a Result or more rules.

Additional information:

The imports at the rule-class are:

  • import com.deliveredtechnologies.rulebook.RuleState;
  • import com.deliveredtechnologies.rulebook.annotation.Given;
  • import com.deliveredtechnologies.rulebook.annotation.Rule;
  • import com.deliveredtechnologies.rulebook.annotation.Then;
  • import com.deliveredtechnologies.rulebook.annotation.When;

The imports at the rule-usage-class are:

  • import com.deliveredtechnologies.rulebook.FactMap;
  • import com.deliveredtechnologies.rulebook.NameValueReferableMap;
  • import com.deliveredtechnologies.rulebook.model.RuleBook;
  • import com.deliveredtechnologies.rulebook.Result;

The import at Spring-configuration is:

  • import com.deliveredtechnologies.rulebook.spring.RuleBookFactoryBean;

Add Support for Defining Rules Without Lambda and the DSL

Rules and Decisions can be very nicely wired up with lambda. But [eventually] there should be support for wiring rules up without lambda and without the DSL (even though, this is very cool :).

What if given(), when() and then() methods could take in Facts, Predicates and Functions, respectively, but also be defined in a Rule class (by the developer)? That might be pretty cool. So, it could be an either/or situation. And thenRules or Decisions could then be added to a Book or mixed with other Rule types.

Cleanup old & unneeded RuleBook code

Specialized Spring support for RuleBook isn't really needed anymore. RuleBook can now be easily used with Spring.

Also, there's some old RuleBook code left over from the early days of RuleBook that's only kept there for backwards compatibility purposes. v1.0 should see that old code removed.

Unable to create an instance of the specified Rule class <> with fact and result type specified.

Hello, just moved to 0.8 from 0.7. Prior to and after the update, I'm getting these debug statements in my output when trying to build rules. However, the rules are being loaded and executed properly as all my testing is passing successfully. I've tried different variations of below and still end up with the log statements. Any ideas?

14:30:59.143 [main] DEBUG c.d.rulebook.lang.RuleBuilder - Unable to create an instance of the specified Rule class 'class com.deliveredtechnologies.rulebook.model.GoldenRule' with fact and result types specified

`public class CanadaRules extends CoRRuleBook{

Override
protected void defineRules() {
addRule(getCalculatorRuleBuilder()
.when(fact -> isRequestTypeDonation(fact.getOne()))
.then((fact, result) -> {
Map<Item,List> appliedItemRateMap = getAppliedItemRateMap(fact.getOne());
result.setValue(createResult(appliedItemRateMap));
}).stop().build());

private RuleBuilder<CalculatorFact, BusinessRulesResult> getCalculatorRuleBuilder() {
return RuleBuilder.create().withFactType(CalculatorFact.class).withResultType(BusinessRulesResult.class);
}
}`

Support for Groovy

Defining rules in Java using Lambda is awesome. But Groovy doesn't currently support Lambda; it uses Groovy closures.

Given how much I personally love Groovy and that Groovy scripting could be used to externalize (and version) RuleBooks, I think RuleBook needs to be Groovy too!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.