kie-dmn/Developer_Guide.md
This module (and all its submodules) contains the code that make up the DMN engine.
The engine is responsible, in very general terms, of
Definitions with additional informations (e.g. ResourceWithConfiguration)FunctionalInterface, since it extends Function<EvaluationContext, Object>CompiledFEELExpression instantiated for statically-interpreted execution of the specific FEEL-expressionCompiledFEELExpression instantiated for code-generated execution of the specific FEEL-expressionCompiledFEELExpression to be executed; please note that a FEEL-expression could represent an aggregation of other sub-expressions; so, in turns, a ProcessedExpression may contain nested onesFunctionalInterface that it is invoked by evaluation objects (e.g. Decision) to actually invoke the generated ProcessedExpression;Definitions and all the DMNNodes obtained by compilationThe simplest way to compile a model is to instantiate a DMNRuntime from its file, e.g.
DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() );
DMNRuntimeUtil has different overrides of the createRuntime method
To validate a dmn model, it is necessary to:
List<DMNProfile> defaultDMNProfiles = DMNAssemblerService.getDefaultDMNProfiles(ChainedProperties.getChainedProperties(ClassLoaderUtil.findDefaultClassLoader()));
DMNValidator validator = DMNValidatorFactory.newValidator(defaultDMNProfiles);
DMNValidator methods; e.g.
List<DMNMessage> validate = validator.validate(
getFile("dmn14simple.dmn"),
VALIDATE_SCHEMA, VALIDATE_MODEL, VALIDATE_COMPILATION);
To execute a dmn model, it is necessary to:
DMNRuntime, built around the given model; e.g.
DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() );
DMNModel out of the former; e.g.
DMNModel dmnModel = runtime.getModel("https://github.com/kiegroup/kie-dmn/itemdef", "simple-item-def" );
DMNContext context = DMNFactory.newContext();
context.set( "Monthly Salary", 1000 );
DMNResult
DMNResult dmnResult = runtime.evaluateAll(dmnModel, context );
DMNDecisionResultPlease note that DMNRuntime has different evaluate* methods
The original xml is first parsed to Definitions by the DMNMarshaller, that is a simple xml-marshaller(DMNAssemblerService#addResourcesAfterRules).
Then, each Definitions is wrapped inside a DMNResource and compiled by DMNCompilerImpl.
Each element of the Definitions is further translated to DMNNode.
Then, the text representing each DMNNode is compiled by the DecisionCompiler and, in turn, by FEELImpl, that would return a ProcessedExpression.
Inside ProcessedExpression, the expression is parsed to org.antlr.v4.runtime.tree.ParseTree by org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser (auto-generated), and that ParsedTree is visited by ASTBuilderVisitor to get the BaseNode ast, that recursively contains all the nested BaseNodes.
Finally, the CompiledFEELExpression to be executed is returned (ProcessedExpression#asCompiledFEELExpression).
The retrieved CompiledFEELExpression could be a statically-interpreted InterpretedExecutableExpression (that wraps the original BaseNode ast) or could be a dynamically-code-generated CompiledExecutableExpression.
In the first case, evaluation is executed by the DMN code as it is statically defined.
In the latter case, code is generated out of the given model. In that code, some variable will be directly written in the generated, speeding up its execution.
Beside that, generated code will invoke the same functions as the interpreted one.
Codegen execution is enabled in two ways:
doCompile boolean in the CompilerContext (CompilerContext.setDoCompile(true))When codegen is enabled, first the model is read and parsed as in the interpreted way; then:
BaseNode ast (by ASTCompilerVisitor)CompiledExecutableExpression (by CompilerBytecodeLoader)CompiledFEELExpression is wrapped and returned inside a CompiledExecutableExpressionDepending on a series of flags ( VALIDATE_SCHEMA, VALIDATE_MODEL, VALIDATE_COMPILATION, ANALYZE_DECISION_TABLE), DMNValidatorImpl executes the validation of the given model.
Behind the scenes, the validation also uses the rule engine and the rules defined in the different kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1()/*.drl files.
This validation is fired inside private List<DMNMessage> validateModel(DMNResource mainModel, List<DMNResource> otherModels)
When DMNRuntime#evaluate* is invoked, a DMNResult is instantiated, containing a map with DMNDecisionResults, initially marked as NOT_EVALUATED.
Then, for each DecisionNode:
DMNDecisionResult is marked as EVALUATINGDecisionNodeImpl#getEvaluator())DMNExpressionEvaluator#evaluate(DMNRuntimeEventManager, DMNResult) method is invoked (see DMNExpressionEvaluator)EvaluationContextImpl is created from the FEELImpl instanceFEELimpl#evaluate(CompiledExpression expr, EvaluationContext ctx) method is invoked, passing the CompiledExpression expression wrapped by the current DMNExpressionEvaluatorProcessedExpression#apply(EvaluationContext) method is invoked (ProcessedExpression is the actual type of CompiledExpression)CompiledFEELExpression executableFEELExpression (that could be an InterpretedExecutableExpression or a CompiledExecutableExpression)ObjectDMNDecisionResult, and that latter is marked as SUCCEEDEDEvery FEEL function is mapped to a concrete class extending BaseFEELFunction.
During execution, this FEELFunction is looked-up by name, then the actual method to be invoked is looked-for, reflectively, based on the given input parameters.
The critical point where this happen is the BaseFEELFunction#getCandidateMethod(EvaluationContext ctx, Object[] originalInput, boolean isNamedParams) method.
Based on a algorithm defined in the ScoreHelper, each invoke method is tested and provided a score. The one with the highest score will be used for actual function evaluation.
Coercion is the feature for which a given object is transformed to an equivalent object of a different type.
One example of that coercion is applied whenever a number, or a string representing a number, is received, in which case it is translated to BigDecimal. Another example is when a method expects a list, and a single object is provided: in that case, the object is coerced to a singleton list. The rules for coercion are the ones provided by the DMN specification.
During invoke method discovery, inside BaseFEELFunction, the given input parameters are coerced to potentially match the ones expected by the current invoke method, and the coerced values are stored to be used for the execution of that specific method.
The main goal for the DMN engine are performance and maintainability.
About the former, whenever important refactoring are done, it would be important to also execute DMN Benchmarks to verify for modification of them, fixing eventual regressions.
About the latter, it would be important to strive for simplicity of reading, instead of too-hard-to-read conciseness. It would also be important to follow goo'ol' rules, like: