Using RuntimeEngine

◷ Reading Time: 9 minutes

Introduction

In order to execute logic, your application should:

  1. Create an execution plan
  2. Create multiple processing engines to execute the requests against the execution plan

This process can be simplified by using RuntimeEngine

For example, the following code creates an instance of an engine to process leave eligibility:

// Load or build a ruleset
var rs = CreateRuleSet();
 
// create an instance of engine
var engine = RuntimeEngine.FromRuleSet(rs, "purchase.xml");
 
// get a data to pass to engine to run
var person = new Person("arash", 10, Gender.Male);
 
// call Run method of engine
var result = engine.Run(person);
 
// use result to retrieve execution context, variable container, notification, outputs...

Creating Runtime Engine

To create an instance of RuntimeEngine, you should use any of the static methods available on the RuntimeEngine type:

In short, you can create your model from different types:

  1. From Spreadsheet document contents
  2. From XML contents
  3. From RuleSet reference

Make sure you do not create the engine every time. Create it once and use it across multiple requests. You can store the engine instance in a static variable.

public class RuntimeEngine : IRuntimeEngine
{
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance from a stream of a spreadsheet document.
    /// </summary>
    /// <param name="stream">A stream to an XML content of a base model</param>
    /// <param name="baseFolder">Path to resolve all dependencies on execution plan creation</param>
    /// <returns></returns>
    public static IRuntimeEngine FromSpreadSheet(Stream spreadSheet, string activeSheet, bool newFormat = true);
 
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance from a stream of XML content.
    /// </summary>
    /// <param name="stream">A stream to XML content of a base model</param>
    /// <param name="baseFolder">Path to resolve all dependencies on execution plan creation</param>
    /// <returns></returns>
    public static IRuntimeEngine FromXml(Stream stream, string baseFolder);
 
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance.
    /// </summary>
    /// <param name="stream">A stream to an XML content of a base model</param>
    /// <param name="ruleSet">Dependency resolver <see cref="IRuleSet"/> to resolve all dependencies on the execution plan creation</param>
    /// <returns></returns>
    public static IRuntimeEngine FromXml(Stream stream, IRuleSet ruleSet);
 
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance.
    /// </summary>
    /// <param name="content">An XML content of a base model</param>
    /// <param name="ruleSet">Dependency resolver <see cref="IRuleSet"/> to resolve all dependencies on execution plan creation</param>
    /// <returns></returns>
    public static IRuntimeEngine FromXml(byte[] content, IRuleSet ruleSet);
 
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance.
    /// </summary>
    /// <param name="content">An XML content of a base model</param>
    /// <param name="baseFolder">Path to resolve all dependencies on execution plan creation</param>
    /// <returns></returns>
    public static IRuntimeEngine FromXml(byte[] content, string baseFolder);
 
    /// <summary>
    /// Creates an <see cref="IRuntimeEngine"/> instance.
    /// </summary>
    /// <param name="ruleSet">A <see cref="IRuleSet"/> that holds all the related models for loading execution plan and executing logic</param>
    /// <param name="ruleSetQuery">Address to the main model within the provided <paramref name="ruleSet"/></param>
    public static IRuntimeEngine FromRuleSet(IRuleSet ruleSet, string ruleSetQuery);
}

From XML File

/* 
   Here is the content binary or stream to the rule file. 
   You could have the rule  
   * stored in file system (folders and files such as XML files in your application folder) 
   * stored in database
   * embedded in assembly
   * stored in cloud storage
   Or any location, no limitation
*/ 
var content = ... // <-- Here you reference the content of the rule
var engine = RuntimeEngine.FromXml(content);

From Package File

/* 
   Here is the content stream to the rule file. 
   You could have the rule:  
   * stored in file system (folders and files such as XML files in your application folder) 
   * stored in database
   * embedded in assembly
   * stored in cloud storage
   Or any location, no limitation
*/ 

// GetStreamForPackage is your method to retrun a stream of package
using (Stream fs = GetStreamForPackage()) 
{
    var loader = new PackageLoader(fs); // create loader from a stream
    var module = loader.Modules.First(); // get the first module
    var engine = RuntimeEngine.FromRuleSet(module.RuleSet, module.Entry);
}

From Spreadsheet

/* 
   Here is the content binary or stream to the rule file. 
   You could have the rule:  
   * stored in file system (folders and files such as XML files in your application folder) 
   * stored in database
   * embedded in assembly
   * stored in cloud storage
   Or any location, no limitation
*/ 
var content = ... // <-- Here you reference the binary content of the Excel document
var engine = RuntimeEngine.FromSpreadSheet(content, "GroupDiscount");  // GroupDiscount is the sheet in the Excel document

Executing Logic

To execute logic, you simply call the ‘Run’ method of the RuntimeEngine instance, which returns the result.

You can pass all your input parameters to the ‘Run’ method. The RuntimeEngine does all the magic.

var person = new Person(); // This is an input to an input parameter of the logic
var address = new Address(); // This is another input to an input parameter of the logic
var result = engine.Run(person, address);

Runtime Result

After calling into Run engine would return an instance of RuntimeResult. If the logic has an Output parameter, return value (i.e., Validation logic), Exception or notification, then you can use RuntimeResult to retrieve those values when execution is finished.

Example

From XML file

In the following example, we load a validation rule from a file called DeprecationValidation.xml and execute it against an input object from the application.

// Creating an engine for validation from a file named DeprecationValidation.xml 
// This engine reference can be shared. There is no need to create it every time.
var eng = RuntimeEngine.FromXml(File.ReadAllBytes("Rules\\Validations\\DeprecationValidation.xml"));
 
// Get the input parameter of the validation rule (i.e., load it from database, input from user interface, call from web api or service, etc.)
var expense = new Expense(){ItemAmount=10, Deprecation = true};
 
// Calling the validation by passing the input values
var res = eng.Run(expense);
 
// Was the object valid? Have the eligibility criteria been met (i.e., were rules satisfied)?
if (!res.Outcome.Value)
{
    // Write (feedback) from rule to the users (i.e., error, warning, info, etc.)
    foreach (var notice in res.Context.Notifications.Default.Notices)
    {
        Console.WriteLine("{0}: {1}", notice.Type, notice.Message);
    }
}

From Excel file

In the following example, we load a decision table named CalculatePremiums from an Excel file called Insurance-Rules.xls and execute the decision table against an input object from the application.

// Creating engine from file named Insurance-Rules.xlsx 
// This engine reference can be shared, there is no need to create it every time.
var engine = RuntimeEngine.FromSpreadSheet(File.OpenRead(@"Insurance-Rules.xlsx"), "CalculatePremiums");
 
// The rule CalculatePremiums requires an input parameter
// Get the input parameter of the validation rule (i.e., load it from the database, input from user interface, call from web api or service, etc.)
var car = new Car()
{
    ModelYear = 2015,
    Style = CarStyle.Compact,
};
 
// Calling the Run on the engine to execute the rules
engine.Run(car);

Please note, you should ‘NOT’ create an engine for processing each request. Simply hold on to the engine reference and reuse it in the future in order to process all your requests. Creating the RuntimeEngine instance is a once-off process for your whole application life cycle. You can do it with either of the following approaches:

  1. in loading of your application
  2. lazily when the first time rules are being executed

Only re-create the RuntimeEngine instance when your rules model has changed.

Run with Parameters

Sometimes, in NL or Validation logic, you want to specifically run a particular logic, then this overload can be used:

var order = new Order();
order.Lines.Add(new OrderLine { Product = { Name = "Case" } });
order.Lines.Add(new OrderLine { Product = { Name = "Sound Card" } });
 
var engine = RuntimeEngine.FromXml(File.OpenRead("ComputerProduct_CaseCannotHaveExtra.xml"));
// order in this case is the input of the whole document, not this specific logic group
var result = engine.Run(new RunParameter("Order"), order);

Use RunParameter to specify the name of Logic

Using Extensions

If your model using extensions they need to be registered on the engine instances before calling to Run method.

var path = "d:\extensions\FlexRule.Extensions.Storage.AzureBlobStorage.dll"
FunctionsRegistry.Register(path);

If there are multiple extensions, the above process should apply on all the required extensions.

Dependencies

When assemblies having dependencies, engine instance might need to know where to find the other dependant assemblies at runtime.

At the engine level, you can register some private directories to look up for dependant assemblies.

For example below code registers a folder to dependant assemblies:

engine.AddAssemblyPrivateLocation(new[] { @"d:\extensions" });

Behind the Scene

Behind the scenes, RuntimeEngine does all the magic:

  1. Creates an execution plan
  2. Manages execution plan life-cycle
  3. Creates an execution engine for a loaded plan
  4. Registers all the transformer and command handlers
  5. Execute the logic using passing parameters
  6. Handles exceptions
  7. Manages notifications and notices
  8. Updates the results when necessary (e.g., in DMN Decision Tables)
  9. Creates Conclusion Log
  10. Registers custom functions
  11. Handles glossaries for a different type of logic

and finally returns a result to your code!

Execution Plan

An execution plan is an optimized orchestration in order to execute a logic. This plan will be created based on your logic model. For each type of logic, there is an execution plan:

LogicExecution Plan TypeExecution Engine Type
Validation LogicValidatorValidatorEngine
Procedural LogicProcedureProcedureEngine
Flow LogicFlowFlowEngine
Workflow LogicWorkflowWorkflowEngine
Decision Table LogicDecisionTableDecisionTableEngine
Natural Language LogicNaturalLanguageValidatorValidatorEngine
Decision Graph LogicDRDFlowEngine

Some of the logic types may not have a specific execution plan or execution engine. These just use other types of execution plans or engines. RuntimeEngine uses these execution plans and execution engines in order to provide a single point of integration to your application.

Updated on July 15, 2020

Was this article helpful?

Related Articles