◷ Reading Time: 9 minutes
Introduction
In order to execute logic, your application should:
- Create an execution plan
- 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:
- From Spreadsheet document contents
- From XML contents
- From RuleSet reference
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:
- in loading of your application
- 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);
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);
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:
- Creates an execution plan
- Manages execution plan life-cycle
- Creates an execution engine for a loaded plan
- Registers all the transformer and command handlers
- Execute the logic using passing parameters
- Handles exceptions
- Manages notifications and notices
- Updates the results when necessary (e.g., in DMN Decision Tables)
- Creates Conclusion Log
- Registers custom functions
- 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:
Logic | Execution Plan Type | Execution Engine Type |
---|---|---|
Validation Logic | Validator | ValidatorEngine |
Procedural Logic | Procedure | ProcedureEngine |
Flow Logic | Flow | FlowEngine |
Workflow Logic | Workflow | WorkflowEngine |
Decision Table Logic | DecisionTable | DecisionTableEngine |
Natural Language Logic | NaturalLanguageValidator | ValidatorEngine |
Decision Graph Logic | DRD | FlowEngine |
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.