Deduction Rules

Deduction rules are at the heart of the deduced framework. They allow developers to link various fields in the application and specify a rule that gets executed when one of those fields changes.

A Simple Example

As an example, let's imagine that we create an object representing yourself. Here are the attributes of that object:

  • bank : represents how much money you have in the bank.
  • wallet : represents how much money you have in your wallet.
  • total : represents how much money you have in total.
Ideally, you'd like the total field to be automatically deduced from the 'bank' and 'wallet' field. In other frameworks, you could implement this by modifying the 'setter' method of both fields to automatically update the total field or you could implement a listener model, hook it up to the right fields and update the total field when a change is detected.

In the deduced framework, this would be accomplished by creating a deduction rule that would be configured like this:

  • Input 1 : bank attribute
  • Input 2 : wallet attribute
  • Output : total attribute
  • Calculate Total Rule :
    return bank+wallet;
Once created, the rule will automatically start listening to the specified inputs and produce the specified output based on the rule.

Rule Definition Language : Java

Deduction rules are implemented using Java. Each rule represents a method that receives the specified inputs as parameters as well as a context variable representing the deduction rule execution context. The rule method is expected to return the rule output.

In the example above, here is what the code would look like once generated

public class DeducedDynamicClass extends 
    org.deduced.rule.DeductionRuleLibrary
{
    public java.lang.Object analyzeRule(
        java.lang.Object[] inputs, 
        org.deduced.RuleExecutionContext context,
        org.deduced.PropertyCollection collection,
        org.deduced.PropertyCollection rule)
    {
        return CalculateTotalRule(
           (java.lang.Integer)inputs[0], 
           (java.lang.Integer)inputs[1],
           context);
    }
    
    public java.lang.Object CalculateTotalRule(
        java.lang.Integer bank,
        java.lang.Integer wallet,
        org.deduced.RuleExecutionContext context,
        org.deduced.PropertyCollection collection,
        org.deduced.PropertyCollection rule)
    {
        // only this part was coded, the rest of the code is generated
        return bank+wallet;
    }
}
   

A rule can invoke any java method necessary. However, it is preferable to keep rules simple and well contained. Rules should be implemented so that they are safe to be used in multiple threads.

Rule Method Library

By default, deduction rules will automatically extends org.deduced.rule.DeductionRuleLibrary. This gives easy access to many utility methods that help in the creation of rules. For instance the rule above might want to use the sum method to ensure the code will handle null inputs gracefully.

return sum(bank,wallet);

The complete list of utility methods can be found in the org.deduced.rule.DeductionRuleLibrary javadoc.

Rule Execution Parameters

When executed, rules receive the following inputs:

  • The specified inputs.
  • The rule execution context.
  • The collection currently executing the rule.
  • The rule definition.
The rule execution context is implemented by the interface org.deduced.RuleExecutionContext. This parameter is used to access objects that are necessary to run certain rule functions. For instance, functions that create new property collections need to have access to this context so that they may use the right model factory.

The rule and collection parameter are made available as a convenience.

Dynamic Changes

Rules are allowed to be created, deleted and changed as the application is running. If the rule definition changes, it will automatically be recompiled and executed on all applicable objects. However, it is recommended to disable a rule while it is being modified. Otherwise it becomes easy to introduce compilation or behavior errors that might change the underlying model in a way that isn't desired.

When a new rule is added and enabled, it will automatically be applied to all the objects to which it apply.

When a new rule is deleted or disabled and enabled, it will automatically be removed from the objects that it was applied to. The outputs of the rule will remain as they were when the rule last executed.

Input Definition

The following types of inputs can be used to execute deduction rules:

  • Instance Reference
  • Static Instance Reference

Instance Reference

Instance Reference objects are used to chain a list of property instance that will indicate where the deduction rule should fetch it's values from before execution.

As a simple example, an input might be defined with the name property. This way, when a rule is executed, it will automatically receive the value contained in the name property.

A more complex example would be an object of type custom that would contain an named property collection in a property named child object. In this case, an instance reference could be defined with the following property instances:

  • child object
  • name
When executed, the rule will automatically attempt to read the child object and fetch the name property as a rule input. If either the child object or the name on the child object is null, then the input received will remain null.

Instance References can also reference objects in a list. To do so, the property instances in the instance reference must include the property of the list itself as well as the property of the objects contained within the list.

For instance, an object of type custom that would contain a list of named property collection in a property named child object list. In this case, an instance reference could be defined with the following property instances:

  • child object list
  • child object
  • name
When executed, the rule will automatically attempt to read the child object list. Next, it will attempt to read all the properties that match child object within the list, which should be all the elements. Next, the rule will attempt to read all the name properties on all the objects that were found in the previous inspection. As a result, the rule will receive a java.util.List containing all the name value of all the child objects.

If the child object list is null, then the rule input will be null.

If the child object list is empty, then the rule input will be an empty list.

If some of the name values in the child objects is null, then the rule input will be list that will contain both null and non null values.

The order of the values in an input that returns a list isn't guaranteed. In the example above, if the custom object contains a child object named "Test" and another one named "Ordering", the input received by the deduction rule might be {"Test", "Ordering"} or {"Ordering", "Test"}.

Static Instance Reference

A static instance reference is an extension of the Instance Reference concept. The difference is that a static instance reference will look for input values by starting in a predefined collection instead of looking in the collection that is currently executing the rule.

For instance, an object of type custom that would contain a rule that contains a Static Instance Reference defined with :

  • A link to a named collection as the static reference.
  • The name property in the input instance list.
In this case, whenever the name property of the named collection would change, the rule would be executed and it would received the changed name as input, even though the custom object has no direct link to the named collection.

Unlike Instance References, defining a list of input properties is optional for a Static Instance Reference. If no input property is defined, the rule will use the static reference value as input. This is useful to assign fixed values as rule inputs.

Output Definition

A rule may only contain one output. If many outputs are needed, developers should create different rule for each desired output. Outputs are defined with an Instance Reference object.

Outputs have additional restrictions in the way the instance reference is configured in order to be valid. The main restriction is that all the instances defined in the rule output must be contained by value and not by reference. This is done to simplify the rule execution model and also to guarantee layers in the deduced applications can't override each other.

Handling Compilation Errors

If a rule doesn't create valid java code, the compilation errors will be stored in the compilation output variable of a rule to help troubleshoot the problem. The rule will also be automatically disabled until it is manually enabled.

Exception Handling

When a rule execution yields an exception, that exception is caught by the rule execution engine and not thrown back to avoid breaking the current application. The error message is automatically stored in the last exception field of the deduction rule. Every time a new exception occurs when the rule executes, the last exception field will be replaced by the new error message.

It is recommended to implement deduction rules so they avoid throwing exceptions. A good way to accomplish this is by using the methods defined in : org.deduced.rule.DeductionRuleLibrary. Those methods perform the necessary checks to avoid most of the common exceptions such as null pointer exceptions.

Inheritance

When a property collection type inherits from another one, it automatically inherits the rule defined in the parent type. However, the child type has the option of overriding this rule by creating a rule of it's own that has the same output defined.

It is possible to create a situation where a property collection type inherits a rule that affects the same output form two different parent types. In such a case, only one of the rules will be effective and override the other one. This situation will generate warning messages in the logs.

Parents overriding children

When a property collection type defines a rule where the output targets a property in one of it's child property collection, that rule will take priority over any rule defined in the child collection that might affect the same output property.

Following that logic, the parent of the parent might also override the same output property. The deeper a parent is in the hierarchy, the more it takes priority when overriding rules.

Precompiling rules

Deduction rules are normally compiled the first time they are required. This compilation time can be avoided by pre-compiling a deduction rule with your application. To do so, a developer must create a class that implements the interface org.deduced.DeductionRuleAnalyzer and implement the rule logic as required. The arguments will be received as an array of values where the order of the parameter matches the order of the rule inputs.

Once such a rule is implemented, compiled and introduced in the application class path, it can then be used by a deduction rule by replacing the code field of a rule with a java class name. For instance, a developer could create a class named org.deduced.MyCustomRule that implements DeductionRuleAnalyzer. He can then put the value "org.deduced.MyCustomRule" in the code field.

Special Inputs : All Property Values

In the base schema org.deduced.Utilities class, there is a property instance named "All Property Values". This instance may be used anywhere within deduction rule inputs.

Using this wild card property instance will tell the deduction rule to fetch all the properties fetched within a collection.