Skip to content

Commit 5d33a8b

Browse files
authored
Allow pre-compilation of rules conditions (#122)
* feat: create rules source and middlewares support * test: create benchmark project and add benchmarks * feat: add compilation functionality for value condition nodes * feat: add various improvements to favor overral rules evaluation performance * refactor: rename classic conditions eval engine * feat: implement remaining multiplicities and operators * test: add new unit tests covering compilation functionalities * refactor: implement additional performance improvements * test: test improvements * refactor: optimize conditions convertion and extract conditions tree analysis logic to separate class * feat: move rules compilation to rules source middleware * feat: add support for compiling whole rule conditions tree * fix: add missing package on unit tests and fix condition on compiled conditions eval engine * refactor: simplify and streamline rules compilation * docs: update expression builder flow documentation * test: add the possibility to run only unit tests via script * test: add unit tests * test: add unit tests * refactor: self-review changes * chore: remove env file * chore: fix static analysis issues found by Codacy * chore: fix static analysis issues found by Codacy (2) * chore: fix static analysis issues found by Codacy (3) * fix: add properties to data models and also deal with changes with in memory data source * chore: rename classic evaluation to interpreted evaluation * chore: build workflow remove unused steps and upgrade actions * chore: code review changes * ci: restore code coverage on github build * fix: remove logic of adding/removing references on local tests script * chore: code review changes * chore: code review changes
1 parent 0dbe5f7 commit 5d33a8b

234 files changed

Lines changed: 11956 additions & 366 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<mxfile host="Electron" modified="2023-02-19T00:26:29.868Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36" etag="7jDJ8ohKprnEyIS5K-Ll" version="20.8.16" type="device"><diagram id="gnvQT8NLbQ4uEHi5X6fw" name="Page-1">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</diagram></mxfile>

rules-framework.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.WebUI", "sr
4040
EndProject
4141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.WebUI.Tests", "tests\Rules.Framework.WebUI.Tests\Rules.Framework.WebUI.Tests.csproj", "{29DC6661-4F0C-46F7-AC91-968700D13C11}"
4242
EndProject
43+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules.Framework.BenchmarkTests", "tests\Rules.Framework.BenchmarkTests\Rules.Framework.BenchmarkTests.csproj", "{16C9F383-3B58-4911-9D26-7FDB907DD0D2}"
44+
EndProject
4345
Global
4446
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4547
Debug|Any CPU = Debug|Any CPU
@@ -98,6 +100,10 @@ Global
98100
{29DC6661-4F0C-46F7-AC91-968700D13C11}.Debug|Any CPU.Build.0 = Debug|Any CPU
99101
{29DC6661-4F0C-46F7-AC91-968700D13C11}.Release|Any CPU.ActiveCfg = Release|Any CPU
100102
{29DC6661-4F0C-46F7-AC91-968700D13C11}.Release|Any CPU.Build.0 = Release|Any CPU
103+
{16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
104+
{16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
105+
{16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
106+
{16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Release|Any CPU.Build.0 = Release|Any CPU
101107
EndGlobalSection
102108
GlobalSection(SolutionProperties) = preSolution
103109
HideSolutionNode = FALSE
@@ -116,6 +122,7 @@ Global
116122
{F5E1BEF6-85CD-4966-94C2-DCA02D824043} = {3125C5B5-0FD5-4370-9E22-A44A3BCEED59}
117123
{7CE82611-FEC1-49E9-91FB-4C3ADF5ED56F} = {AEE746EC-CEAA-4892-8C29-0CAAB97A23A8}
118124
{29DC6661-4F0C-46F7-AC91-968700D13C11} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F}
125+
{16C9F383-3B58-4911-9D26-7FDB907DD0D2} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F}
119126
EndGlobalSection
120127
GlobalSection(ExtensibilityGlobals) = postSolution
121128
SolutionGuid = {FA9D4C31-972B-49C2-9F63-C56ED766DAB0}

run-tests.ps1

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
param ([switch] $OnlyRunUnitTests)
2+
13
$globalTools = dotnet tool list -g
24
$reportGeneratorTool = $globalTools | Select-String -Pattern "dotnet-reportgenerator-globaltool"
35

@@ -9,21 +11,11 @@ $reportTimestamp = [DateTime]::UtcNow.ToString("yyyyMMdd_hhmmss")
911
$currentDir = (Get-Location).Path
1012
$coverageDir = "$currentDir\\coverage-outputs\\$reportTimestamp\\"
1113

12-
# Run with current core "Rules.Framework" package version on other packages
13-
dotnet test .\rules-framework.sln -m:1
14-
15-
# Use project reference to "Rules.Framework" now
16-
dotnet add src\Rules.Framework.Providers.InMemory\Rules.Framework.Providers.InMemory.csproj reference src\Rules.Framework\Rules.Framework.csproj
17-
dotnet add src\Rules.Framework.Providers.MongoDb\Rules.Framework.Providers.MongoDb.csproj reference src\Rules.Framework\Rules.Framework.csproj
18-
dotnet add src\Rules.Framework.WebUI\Rules.Framework.WebUI.csproj reference src\Rules.Framework\Rules.Framework.csproj
19-
20-
# Run again with project reference
21-
dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1
22-
23-
# Remove project reference to "Rules.Framework"
24-
dotnet remove src\Rules.Framework.Providers.InMemory\Rules.Framework.Providers.InMemory.csproj reference src\Rules.Framework\Rules.Framework.csproj
25-
dotnet remove src\Rules.Framework.Providers.MongoDb\Rules.Framework.Providers.MongoDb.csproj reference src\Rules.Framework\Rules.Framework.csproj
26-
dotnet remove src\Rules.Framework.WebUI\Rules.Framework.WebUI.csproj reference src\Rules.Framework\Rules.Framework.csproj
14+
if ($OnlyRunUnitTests) {
15+
dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1 --filter 'Category=Unit'
16+
} else {
17+
dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1
18+
}
2719

2820
reportgenerator -reports:"$($coverageDir)*\\*.xml" -targetdir:"$($coverageDir)" -reporttypes:Cobertura
2921

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
21
namespace Rules.Framework.Providers.MongoDb.DataModel
32
{
3+
using System;
4+
using System.Collections.Generic;
5+
using MongoDB.Bson;
46
using MongoDB.Bson.Serialization.Attributes;
7+
using MongoDB.Bson.Serialization.Options;
58
using Rules.Framework.Core;
69

710
[BsonKnownTypes(typeof(ComposedConditionNodeDataModel), typeof(ValueConditionNodeDataModel))]
811
internal class ConditionNodeDataModel
912
{
10-
[BsonRepresentation(MongoDB.Bson.BsonType.String)]
13+
public ConditionNodeDataModel()
14+
{
15+
this.Properties = new Dictionary<string, object>(StringComparer.Ordinal);
16+
}
17+
18+
[BsonRepresentation(BsonType.String)]
1119
public LogicalOperators LogicalOperator { get; set; }
20+
21+
[BsonDictionaryOptions(Representation = DictionaryRepresentation.Document)]
22+
public IDictionary<string, object> Properties { get; set; }
1223
}
13-
}
24+
}

src/Rules.Framework.Providers.MongoDb/RuleFactory.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,13 @@ private static IConditionNode<TConditionType> ConvertConditionNode(
9393
composedConditionNodeBuilder.AddCondition(cnb => ConvertConditionNode(cnb, child));
9494
}
9595

96-
return composedConditionNodeBuilder.Build();
96+
var composedConditionNode = composedConditionNodeBuilder.Build();
97+
foreach (var property in composedConditionNodeDataModel.Properties)
98+
{
99+
composedConditionNode.Properties[property.Key] = property.Value;
100+
}
101+
102+
return composedConditionNode;
97103
}
98104

99105
private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode<TConditionType> valueConditionNode) => new ValueConditionNodeDataModel
@@ -103,12 +109,13 @@ private static IConditionNode<TConditionType> ConvertConditionNode(
103109
DataType = valueConditionNode.DataType,
104110
Operand = valueConditionNode.Operand,
105111
Operator = valueConditionNode.Operator,
112+
Properties = valueConditionNode.Properties.ToDictionary(x => x.Key, x => x.Value, StringComparer.Ordinal),
106113
};
107114

108115
private static IConditionNode<TConditionType> CreateValueConditionNode(IConditionNodeBuilder<TConditionType> conditionNodeBuilder, ValueConditionNodeDataModel conditionNodeDataModel)
109116
{
110117
TConditionType conditionType = Parse<TConditionType>(conditionNodeDataModel.ConditionType);
111-
return conditionNodeDataModel.DataType switch
118+
var valueConditionNode = conditionNodeDataModel.DataType switch
112119
{
113120
DataTypes.Integer => conditionNodeBuilder.AsValued(conditionType)
114121
.OfDataType<int>()
@@ -153,6 +160,13 @@ private static IConditionNode<TConditionType> CreateValueConditionNode(IConditio
153160
.Build(),
154161
_ => throw new NotSupportedException($"Unsupported data type: {conditionNodeDataModel.DataType}."),
155162
};
163+
164+
foreach (var property in conditionNodeDataModel.Properties)
165+
{
166+
valueConditionNode.Properties[property.Key] = property.Value;
167+
}
168+
169+
return valueConditionNode;
156170
}
157171

158172
private static T Parse<T>(string value)
@@ -174,6 +188,7 @@ private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNod
174188
{
175189
ChildConditionNodes = conditionNodeDataModels,
176190
LogicalOperator = composedConditionNode.LogicalOperator,
191+
Properties = composedConditionNode.Properties.ToDictionary(x => x.Key, x => x.Value, StringComparer.Ordinal),
177192
};
178193
}
179194

src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ namespace Rules.Framework.Builder
44
using System.Collections.Generic;
55
using System.Linq;
66
using Rules.Framework.Evaluation;
7-
using Rules.Framework.Evaluation.ValueEvaluation;
8-
using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers;
7+
using Rules.Framework.Evaluation.Compiled;
8+
using Rules.Framework.Evaluation.Compiled.ConditionBuilders;
9+
using Rules.Framework.Evaluation.Interpreted;
10+
using Rules.Framework.Evaluation.Interpreted.ValueEvaluation;
11+
using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers;
912
using Rules.Framework.Source;
1013
using Rules.Framework.Validation;
1114

@@ -23,16 +26,35 @@ public ConfiguredRulesEngineBuilder(IRulesDataSource<TContentType, TConditionTyp
2326
public RulesEngine<TContentType, TConditionType> Build()
2427
{
2528
var rulesSourceMiddlewares = new List<IRulesSourceMiddleware<TContentType, TConditionType>>();
26-
var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory();
2729
var dataTypesConfigurationProvider = new DataTypesConfigurationProvider(this.rulesEngineOptions);
2830
var multiplicityEvaluator = new MultiplicityEvaluator();
2931
var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer<TConditionType>();
3032

31-
var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider);
33+
IConditionsEvalEngine<TConditionType> conditionsEvalEngine;
3234

33-
var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions);
35+
if (this.rulesEngineOptions.EnableCompilation)
36+
{
37+
// Use specific conditions eval engine to use compiled parts of conditions tree.
38+
var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider();
39+
var valueConditionNodeCompilerProvider = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider);
40+
var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder<TConditionType>(valueConditionNodeCompilerProvider, dataTypesConfigurationProvider);
41+
conditionsEvalEngine = new CompiledConditionsEvalEngine<TConditionType>(conditionsTreeAnalyzer, this.rulesEngineOptions);
3442

35-
var conditionsEvalEngine = new ConditionsEvalEngine<TConditionType>(deferredEval, conditionsTreeAnalyzer);
43+
// Add conditions compiler middleware to ensure compilation occurs before rules
44+
// engine uses the rules, while also ensuring that the compilation result is kept on
45+
// data source (avoiding future re-compilation).
46+
var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware<TContentType, TConditionType>(ruleConditionsExpressionBuilder, this.rulesDataSource);
47+
rulesSourceMiddlewares.Add(compilationRulesSourceMiddleware);
48+
}
49+
else
50+
{
51+
// Use interpreted conditions eval engine that runs throught all conditions and
52+
// interprets them at each evaluation.
53+
var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory();
54+
var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider);
55+
var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions);
56+
conditionsEvalEngine = new InterpretedConditionsEvalEngine<TConditionType>(deferredEval, conditionsTreeAnalyzer);
57+
}
3658

3759
var conditionTypeExtractor = new ConditionTypeExtractor<TContentType, TConditionType>();
3860

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Rules.Framework.Core
2+
{
3+
internal static class ConditionNodeProperties
4+
{
5+
internal static class CompilationProperties
6+
{
7+
public static string CompiledDelegateKey => $"{Prefix}_compiledDelegate";
8+
public static string IsCompiledKey => $"{Prefix}_isCompiled";
9+
public static string Prefix => "_compilation";
10+
}
11+
}
12+
}

src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
namespace Rules.Framework.Core.ConditionNodes
22
{
3+
using System;
34
using System.Collections.Generic;
45
using System.Diagnostics;
56
using System.Linq;
67

78
/// <summary>
8-
/// A composed condition node which aggregates a set of child condition nodes and defines a logical operator to apply to them.
9+
/// A composed condition node which aggregates a set of child condition nodes and defines a
10+
/// logical operator to apply to them.
911
/// </summary>
10-
/// <typeparam name="TConditionNode">The condition type that allows to filter rules based on a set of conditions.</typeparam>
12+
/// <typeparam name="TConditionNode">
13+
/// The condition type that allows to filter rules based on a set of conditions.
14+
/// </typeparam>
1115
[DebuggerDisplay("Composed condition: apply {LogicalOperator.ToString(),nq} operator for {System.Linq.Enumerable.Count(ChildConditionNodes),nq} nodes")]
1216
public class ComposedConditionNode<TConditionNode> : IConditionNode<TConditionNode>
1317
{
@@ -20,6 +24,7 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable<ICond
2024
{
2125
this.LogicalOperator = logicalOperator;
2226
this.ChildConditionNodes = childConditionNodes;
27+
this.Properties = new Dictionary<string, object>(StringComparer.Ordinal);
2328
}
2429

2530
/// <summary>
@@ -32,11 +37,36 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable<ICond
3237
/// </summary>
3338
public LogicalOperators LogicalOperator { get; }
3439

40+
/// <summary>
41+
/// Gets the condition node properties.
42+
/// </summary>
43+
public IDictionary<string, object> Properties { get; }
44+
3545
/// <summary>
3646
/// Clones the condition node into a different instance.
3747
/// </summary>
3848
/// <returns></returns>
3949
public IConditionNode<TConditionNode> Clone()
40-
=> new ComposedConditionNode<TConditionNode>(this.LogicalOperator, this.ChildConditionNodes?.Select(cn => cn.Clone()));
50+
=> new ComposedConditionNode<TConditionNode>(this.LogicalOperator, this.ChildConditionNodes.Select(cn => cn.Clone()).ToList().AsReadOnly());
51+
52+
/// <summary>
53+
/// Determines whether the specified <see cref="System.Object"/>, is equal to this instance.
54+
/// </summary>
55+
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
56+
/// <returns>
57+
/// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance;
58+
/// otherwise, <c>false</c>.
59+
/// </returns>
60+
public override bool Equals(object obj) => obj is ComposedConditionNode<TConditionNode> node && EqualityComparer<IEnumerable<IConditionNode<TConditionNode>>>.Default.Equals(this.ChildConditionNodes, node.ChildConditionNodes) && this.LogicalOperator == node.LogicalOperator && EqualityComparer<IDictionary<string, object>>.Default.Equals(this.Properties, node.Properties);
61+
62+
/// <summary>
63+
/// Returns a hash code for this instance.
64+
/// </summary>
65+
/// <returns>
66+
/// A hash code for this instance, suitable for use in hashing algorithms and data
67+
/// structures like a hash table.
68+
/// </returns>
69+
public override int GetHashCode()
70+
=> HashCode.Combine(this.ChildConditionNodes, this.LogicalOperator, this.Properties);
4171
}
4272
}

0 commit comments

Comments
 (0)