Back to Incubator Kie Drools

kie-dmn - Developer Guide

kie-dmn/Developer_Guide.md

10.2.014.3 KB
Original Source
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -->

kie-dmn - Developer Guide

This module (and all its submodules) contains the code that make up the DMN engine.

The engine is responsible, in very general terms, of

  1. compile the dmn model
  2. validate it
  3. evaluate it, against a given input; please note that execution could be statically-interpreted or dynamically-code-generated (see later on Interpreted vs Codegen)

Structure

DTOs

* Definitions: the direct representation of the original XML

* DMNResource: a wrapper used to forward Definitions with additional informations (e.g. ResourceWithConfiguration)

* DMNNode: the ast representation of high-level nodes inside the model; it map to BusinessKnowledge, Decision, DecisionService, ItemDef, or InputData

* BaseNode: the ast representation of low-level nodes inside the model (e.g. Boolean, AtLiteral, UnaryTest, String, ecc.)

* CompiledFEELExpression: the object that represents and actually executes a specific FEEL-expression; it is also a FunctionalInterface, since it extends Function<EvaluationContext, Object>

* InterpretedExecutableExpression: the CompiledFEELExpression instantiated for statically-interpreted execution of the specific FEEL-expression

* CompiledExecutableExpression: the CompiledFEELExpression instantiated for code-generated execution of the specific FEEL-expression

* ProcessedExpression: the result of the compilation of a specific FEEL-expression; in turns, it wraps the actual CompiledFEELExpression 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 ones

* DMNExpressionEvaluator: a FunctionalInterface that it is invoked by evaluation objects (e.g. Decision) to actually invoke the generated ProcessedExpression;

* DMNModel: the high-level result of the compilation, to be used to invoke the overall evaluation; it contains Definitions and all the DMNNodes obtained by compilation

* DMNResult: an instance that encapsulates all the information resulting from a DMN service invocation;

* DMNDecisionResult: an instance that stores the result of the evaluation of a decision

Code usage

Compilation

The simplest way to compile a model is to instantiate a DMNRuntime from its file, e.g.

java
     DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() );

DMNRuntimeUtil has different overrides of the createRuntime method

Validation

To validate a dmn model, it is necessary to:

  1. get an instance of DMNValidator, built with some DMNProfile; e.g.
    java
      List<DMNProfile> defaultDMNProfiles = DMNAssemblerService.getDefaultDMNProfiles(ChainedProperties.getChainedProperties(ClassLoaderUtil.findDefaultClassLoader()));
      DMNValidator validator = DMNValidatorFactory.newValidator(defaultDMNProfiles);
    
  2. invoke one of the DMNValidator methods; e.g.
    java
       List<DMNMessage> validate = validator.validate(
                 getFile("dmn14simple.dmn"),
                 VALIDATE_SCHEMA, VALIDATE_MODEL, VALIDATE_COMPILATION);
    

Execution

To execute a dmn model, it is necessary to:

  1. get an instance of DMNRuntime, built around the given model; e.g.
    java
      DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() );
    
  2. get the instance of DMNModel out of the former; e.g.
    java
      DMNModel dmnModel = runtime.getModel("https://github.com/kiegroup/kie-dmn/itemdef", "simple-item-def" );
    
  3. instantiate a DMNContext with the required input; e.g.
    java
      DMNContext context = DMNFactory.newContext();
      context.set( "Monthly Salary", 1000 );
    
  4. get the DMNResult
    java
       DMNResult dmnResult = runtime.evaluateAll(dmnModel, context );
    
  5. depending on the model, the actual result could be stored in the given context, or inside a DMNDecisionResult

Please note that DMNRuntime has different evaluate* methods

Execution flows

Compilation

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).

Interpreted vs Codegen

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:

  1. adding the DoCompileFEELProfile to the FEEL instantiation
  2. setting the 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:

  1. source code is generated out of the given BaseNode ast (by ASTCompilerVisitor)
  2. code is compiled in-memory to a CompiledExecutableExpression (by CompilerBytecodeLoader)
  3. the above CompiledFEELExpression is wrapped and returned inside a CompiledExecutableExpression

Validation

Depending 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)

Execution

When DMNRuntime#evaluate* is invoked, a DMNResult is instantiated, containing a map with DMNDecisionResults, initially marked as NOT_EVALUATED.

Then, for each DecisionNode:

  1. the mapped DMNDecisionResult is marked as EVALUATING
  2. the associated DMNExpressionEvaluator is retrieved (DecisionNodeImpl#getEvaluator())
  3. the DMNExpressionEvaluator#evaluate(DMNRuntimeEventManager, DMNResult) method is invoked (see DMNExpressionEvaluator)
  4. inside the concrete implementation, a new EvaluationContextImpl is created from the FEELImpl instance
  5. the FEELimpl#evaluate(CompiledExpression expr, EvaluationContext ctx) method is invoked, passing the CompiledExpression expression wrapped by the current DMNExpressionEvaluator
  6. in turns, the ProcessedExpression#apply(EvaluationContext) method is invoked (ProcessedExpression is the actual type of CompiledExpression)
  7. this result in invocation of the wrapped CompiledFEELExpression executableFEELExpression (that could be an InterpretedExecutableExpression or a CompiledExecutableExpression)
  8. this, recursively, iterate over all the nested expressions, invoking the FEELFunctions mapped to any expression, until a given final result is provided, as Object
  9. then, the returned object is coerced to the type requested by the decision, the result is stored in the mapped DMNDecisionResult, and that latter is marked as SUCCEEDED

BaseFEELFunction

Every 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

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.

Development guidelines

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:

  • self-explanatory method names
  • self-resilient methods, i.e. a method should work by itself, and eventually manage failures, without depending on some status/assumption provided by invoking code
  • meaningfully property/variable names
  • focused classes, dedicated to a clear and specific task
  • unit-test methods as much as possible, especially newly created ones