This article will show you how to integrate MRules in a project based on Spring. More precisely, we will see here how to use dependency injection with MRules and Spring.
Dependency injection is a mechanism allowing to implement inversion of control. Programs using dependency injection are loosely coupled, as dependencies between software components are resolved dynamically at runtime instead of being hard coded and static.
MRules supports native CDI specified by Java EE 6 since version 1.3.0. But the Spring implementation is more powerful and can be used with JDK 1.5.
So the version 1.9.0 of MRules is shipped with a brand new extension, providing support for Spring dependency injection. See below how to use it.
Add extension to you project
The extension is shipped as a Maven dependency. A transitive dependency allows to retrieve the library core. Only one dependency declaration is necessary.
If you are using on your project the new annotation-oriented way to configure Spring (which is in my opinion far more elegant than the old fashioned XML files) then you will have two solutions available to integrate MRules :
With the factory
In the Spring configuration class, you will have to declare a Bean returning an instance of the MRules factory. You can extend the factory class in order to read configuration from external configuration file, database, or any other way. Then Spring will automatically use the factory instance to instanciate the rule engine. Example :
* Creates a rule engine, managed by Spring via the MRules Factory Bean.
* Spring will inject your rule engine instance in this field at runtime.
// Use the rule engine ...
Without the factory
It’s almost the same as the factory way, but you instanciate your rule engine directly in the Spring config class instead of delegating this operation to the factory. And you use you rule engine instance the same way.
It’s a little less flexible, but may be enough depending on the context.
* Creates a rule engine, managed by Spring via the MRules Factory Bean.
This extension, compiled against MRules 1.9.0 and equally versionned, eases MRules integration in a spring.io based project. The extension is shipped under the form of a Maven module, but is also available for download in the dedicated page, with the associated source code.
A demo project has also been created to illustrate its usage.
We are proud to announce that the version 1.9.0 of MRules is released.
This version brings lots of new features, allowing to write richer and simpler rules. Amongst the brand new evolutions, the most important is the introduction of functions support, allowing to mutualize frequently invoked rules.
Also, a new demonstration has been put online, consisting in solving with MRules any grid of the famous Sudoku puzzle game. This demonstration also integrates a Drools solver, allowing to compare rules complexity and performances.
This version brings few changes for developper API (Addon and extension development) and standard usage is not modified.
This article will provide some tips on how to optimize performances when using MRules. Depending on the version, some of them become unuseful, as the internal optimizer becomes more and more powerful. This will be precised each time.
Compilation (coupled with optimization) is the first step of the life cycle of a rule set. MRules provides two modes for this : the STANDARD mode and the LIGHT mode.
In a general way, the STANDARD mode should be used everytime it’s possible, meaning when you know the structure of the input and output Java beans.
The LIGHT mode allows greater flexibility, but some optimizations can’t be performed if using it. For example, in STANDARD mode, constant values are cast only once at compilation type, in order to correspond to target type. In LIGHT mode, they are cast at runtime, at each execution.
Choose the right rule implementation
To perform actions based on the value of input data, the most common way is to perform an evaluation. For example:
IF (input.value == "A") THEN ACTION "A" IF (input.value == "B") THEN ACTION "B" IF (input.value == "C") THEN ACTION "C" IF (input.value == "D") THEN ACTION "D"
This can lead to complex rules and to more computations than necessary.
To avoid this, the INDEX rule is very adapted to this type of cases:
INDEX(input.value) WHEN "A" THEN ACTION "A" WHEN "B" THEN ACTION "B" WHEN "C" THEN ACTION "C" WHEN "D" THEN ACTION "D"
The “Transportation fees computation” demo shows a use case written using the two techniques.
The EVAL condition (used to perform evaluation, for example “value == otherValue”) is generally written using three inputs:
source: the first value to be compared
operator: the evaluation to perform (equality, greater than, lesser than, …)
reference: the second value to be compared
The name of the input values is choosen accordingly to how they are treated: if types are different, the reference is cast to correspond to the source type, so that the evaluation can be made.
So, if one of the values is a constant, it should be the “reference”, so that it will be cast at compilation time. If not, the cast can not be made and the variable value will be cast at each execution to correspond to the constant type.
IF ("125" IN input.collectionOfInteger) THEN ACTION "A"
In this case, all integer values in the collection will be cast to Strings, and then the engine will check if the String “125” is present in the temporary collection of Strings.
IF (input.collectionOfInteger CONTAINS "125") THEN ACTION "A"
Then the String “125” will be cast as Integer at compilation time and no unuseful operation performed at execution.
Starting from version 1.8.0, the optimizer detects this situation and automatically corrects it.
Use variables when necessary
If some complex operation are performed several times, it might be useful to store the result in a variable to avoid double (or more) computations.
IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "A") THEN ACTION "A" IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "B") THEN ACTION "B"
In this case, the SPLIT might be performed twice. Better write:
$tempArray = SPLIT("A;B;C;D") IF (input.value1 IN $tempArray AND input.value2 == "A") THEN ACTION "A” IF (input.value1 IN $tempArray AND input.value2 == "B") THEN ACTION "B"
Versions anterior to 1.7.0 will try to re-order conditions to evaluate first the simplest ones
Version 1.7.0 (and more) will transform the result of the SPLIT operation into a constant value in the previous case (as the splitted value is a constant).
Version 1.8.0 will be able to cache the result of the SPLIt operation (if not performed on a constant value) and reuse it.
MRules is based on Java reflection API to access input and output data. This API becomes faster and faster at each version of the JRE, but it stays slower than the compiled code.
If you are running MRules using a JRE 7 or greater, the extension allowing to generate bytecode when possible instead of using reflexion might be a good choice.
For public fields and methods of the input / output beans, no reflexion will be used. Global performances at execution will be greatly improved (up to 60 % faster). The generated bytecode is cached and generated only once for a given method, even if the rule engine is recompiled. It is also shared between different rule engines if the Java beans are the same.
New versions provide more and more features and optimizations, allowing rules writers to write the rules in a more intuitive way and to not being aware of all subtleties of performance improvements.
The version 1.8.0 is a major version concerning performances, at all levels. The cache usage has been improved and is more accurate. The internal optimizer is more powerful. Some parts of the library code have been reworked to be always faster.
So don’t hesitate and always upgrade to the latest version !
This version brings lots of new features: new operators, more configuration possibilities and an enriched data access framework.
Also, a consequent optimization project has been realized, impacting all life cycle phases of a rule engine instance, from compilation to execution. Amongst these optimizations, rule rewrite to optimize their execution, cache improvements, input data reading speed-up, etc.
This version brings major changes for developper API (Addon and extension development) but standard usage is not modified.
This article is the first of a new series, which aims to provide tips and tricks on avanced MRules features. We hope that you will find it useful and would be pleased to receive comments on what you did or did not like.
So, this first How To tutorial is about how MRules deals with Collections. As MRules is designed to be used by non-technical people, the way to design rules must be as natural, simple and concise as possible.
So, support of Collections is native and lots of tools are included to handle them:
You can count the number of entries in a list, eventually including only some of them in the counting depending on conditions.
You can sum the elements, compute averages, etc …
You can iterate on the elements.
Excel users will find out that the concept of the “SUM.IF” formula is present, and extended to other operations. To illustrate, we’ll use a very simple example. The case consists in performing operations on a company employees. The input data is very simple:
A List of Employees
Each Employee is:
A birth date
A daily Cost
With MRules, we will compute:
The number of “young” employees (who are less than 30 years old)
The total daily cost of the employees
The percentage of women in the company
A flag checking that all employees are paid at least 600
The first rule to write is an action, which consists in setting a value into an output field.
So we’ll use the SET action, with the “numberOfYoungEmployees” output property as a target. The value to set, the source, is not directly accessible, we have to use the COUNT accessor to compute it. The count is applied on the “employee” input property as the target. But not all employees should be taken into account: so we have to add a condition. This condition will evaluate if age of the persons is less than 30 years. It has three parameters:
The source: the employee’s age, which must be computed from the birth date.
The operator: LT for Less Than.
The reference: simply the value”30″.
Now, computing the age is simple. Date operations are included in MRules and will be the objective of the next How-To. Here, we have to compute the difference beween Now and the birth date in years. So the DATEDIFF accessor will be used, with the following parameters:
The second rule is quite simple compared to the first one. It also consists in setting a value into an output field, so the SET action with the “totalDailyCost” output property as a target will be used.
The source is a sum of the employees’ daily costs, so the SUM accessor will be used, with the “employee” input property as the source parameter, and “dailyCost” property as the property parameter. This second parameters indicates that the daily cost must be read from the iterated element and that this value must be summed.
The objective of this rule is to show two advanced feature:
One offered by the PROPERTY Accessor (allowing to read data from input). It accepts a “source” parameter allowing to read the property from outside the default read base of the context.
The other one offered by the Evaluation Condition. The “iterationOperator” parameter allows to perform several times the condition all all elements of an iterable and to combine the result with a logical operator (AND, OR, …).
So, by combining these two features, this rule is written in a very simple way :
The PROPERTY accessor is told to iterate on all employees and to retrieve “name” property of all iterated objects. The ADDALL action will then iterate on all names and add them to the list. Bby using the ADDALL function, the rule will be more efficient, because less cache access will be performed.
Combining all previously written rules, here is the final rule set configuration:
Using the tools seen in this tutorial, it’s no easy to aggregate Collection values. We can imagine to:
Check if the mean women salary is greater or lesser than the mean men salary.
Compute the mean salary of young women.
Count old men with a salary less than 600.
etc.. Everything is possible.
Also note that you can dynamically create collections to iterate on, using the SPLIT accessor. For example, the following rule will perform five iterations, with the values “10” to “50”, and print them:
MRules 1.6.1 brings minor improvements on types and conversions handling, more specifically reguarding complex cases involving Collections. Moreover, partial support for arrays in SPLIT Accessor has been removed to prefer Collection, as arrays are not fully supported by other Addons. Global array support may be added in future releases. The release notes provide details on all modifications.
The version 1.6.0 of MRules is released. Following a user request, it brings possibility to perform operations on dates and times), like adding duration (in years, mont, days, hours, …) or computing difference between dates (also in years, mont, days, hours, …). All date types are handled, including the java.time package if the JDK8 extension is used. Other new small features were added (like computing the absolute value of a number). The release notes provide details on all modifications.
A problem on date parsing has been reported by some users. Due to improvements made to date / time conversions and to the introduction of the JDK8 java.time package support, the time part could be lost when transforming a String to a java.util.Date. This problem is solved in bugfix version 1.5.1.
Please update your dependencies to stay up-to-date.