Custom Functions in Power Flows DMN
Learn more about decision engines, particularly Power Flows DMN.
Join the DZone community and get the full member experience.
Join For FreeThis article will be dedicated to modern and interesting decision engines, and to be precise, I will focus on one in particular — Power Flows DMN. This project is a library written in Java with open-source code and is available on GitHub. There are many standards and notations for decision engines. One of the most popular is OMG DMN 1.1, and the following project was based on such.
The basic idea of the decision engine is to evaluate rules and return the result. Different engines support different languages to create dynamically calculated expressions. Thanks to this approach, the rules are more generic and re-usable. They often allow us to describe rules that are simply not possible statically.
But a good decision engine should be able to call functions within dynamic expressions. And it would be best if you could define your own ones. By browsing the DMN Power Flows source code, we can see that this functionality has been added. Let's look at the possibilities offered by the tool.
The engine itself supports the evaluation in the following languages:
a) FEEL
b) Juel
c) Groovy
d) MVEL
e) JavaScript
But can we write our own functions for each language and use it in the expression? It turns out — yes!
Let's say our example expression will look like:
testMethod(x, 1) == "I'm no. 1 test static method"
In addition, we assume that the above expression is written in JavaScript. In this example, we wanted to have the ability to use the testMethod
function in expressions so that the user configuring the business rules does not have to duplicate the complex functionality calculating the result, but instead, they used a concise expression consisting only of calling the function.
Let's move to the code. The method to the name used in the expression can be bound with its implementation in Java. Yes, we can make it available to any language used in expressions, but it must be implemented in Java. As for the implementation methods, two are supported:
a) Static methods
b) Instance methods
The first one can be used in any of the supported programming languages. Instance methods work for JavaScript and Groovy.
Static Methods
Let's start with the configuration. First, we need a class defining the methods. For example, check out the following listing:
public class MySampleClass {
private final String value;
public MySampleClass(final String value) {
this.value = value;
}
public String mySampleInstanceMethod(final int firstParam, final String secondParam) {
return value + "I'm no. " + firstParam + " " + secondParam + " instance method;
}
public static String mySampleStaticMethod(final String firstParam, final int secondParam) {
return "I'm no. " + secondParam + " " + firstParam + " static method;
}
}
Methods in the class must be public. The above class defines two methods. One of them is static; the other is not. The instance method will be used by calling it on an instance of the MySampleClass
class. We have to configure the above methods so that they will be visible to the decision engine.
Method method = MethodSource.class.getMethod("mySampleStaticMethod", String.class, Integer.TYPE);
List<MethodBinding> methodBindings = new ArrayList<>();
methodBindings.add(new StaticMethodBinding("testMethod", method));
First of all, we need to get the method object from the given class. Next, we create a StaticMethodBinding
class (a type of MethodBinding
) object and add it to the collection. The collection can contain many objects of the mentioned class. The first argument of StaticMethodBinding
constructor is the name available in expression. In our example, it's the testMethod
:
DecisionEngineConfiguration decisionEngineConfiguration = new DefaultDecisionEngineConfiguration().methodBindings(methodBindings);
For a prepared collection, we set to the decision engine configuration:
DecisionEngine decisionEngine = decisionEngineConfiguration.configure();
And from the configuration, we can get the DecisionEngine
instance. At the end, we can evaluate the decision. It's important to construct decision variables. Since our example expression contains variable x
, there is a need to set the variable:
Map<String, Serializable> vars = new HashMap<>();
vars.put("x", "text");
DecisionVariables decisionVariables = new DecisionVariables(vars);
Decision decision = //your decision object containing expression with custom function in rules
DecisionResult decisionResult = decisionEngine.evaluate(decision, decisionVariables);
Instance Methods
In case we want to use an instance method, the configuration approach is very similar. The only difference is how we get the method from the class. We can perform it as follows:
MethodSource theInstance = new MethodSource("Hello! ");
Method method = MethodSource.class.getMethod("mySampleInstanceMethod", Integer.TYPE, String.class);
List<MethodBinding> methodBindings = new ArrayList<>();
methodBindings.add(new InstanceMethodBinding("testMethod", method, () -> theInstance));
Having prepared the methodBindings
collection, we are able to configure and get DecisionEngine
instance like in the static method example.
Of course, we can mix MethodBinding
types in methodBindings
collection and the decision engine accepts both typesStaticMethodBinding
and InstanceMethodBinding
together for one configuration.
Opinions expressed by DZone contributors are their own.
Comments