Skip to content

Commit c6fdd9d

Browse files
authored
Performance improvements (#148)
* refactor: performance improvements to core package * refactor: use built-in in-memory provider on benchmarks * fix: add protections to properties dictionary to disallow a length less than zero * refactor: use array instead of IEnumerable on for child conditions on data models * fix: fix failing tests * refactor: upgrade all projects to C# 10.0 lang version * feat: add additional validations to properties dictionary and dictionary slim * test: add unit tests for new dictionaries * fix: assume ExpandoObject when type object is requested to desserialize content on MongoDB * test: convert MongoDB scenario tests to also be executed with compilation enabled * test: add MongoDB data source to benchmarks execution * refactor: improvements to MongoDB data source implementation * refactor: general improvements to MongoDB data source implementation * fix: fix test from MongoDB data source implementation * test: add scope to existent MongoDB data source scenario-based test * refactor: mark Condition default ctor as obsolete * chore: fix benchmarks tear up/down benchmark reference * refactor: fix codacy reported issues * refactor: fix codacy reported issues * refactor: code review changes * refactor: code review changes * fix: resolve failing unit tests
1 parent e5d4a2c commit c6fdd9d

59 files changed

Lines changed: 2747 additions & 575 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.
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
21
namespace Rules.Framework.Providers.MongoDb.DataModel
32
{
4-
using System.Collections.Generic;
53
using MongoDB.Bson.Serialization.Attributes;
64

75
[BsonDiscriminator("composed")]
86
internal sealed class ComposedConditionNodeDataModel : ConditionNodeDataModel
97
{
10-
public IEnumerable<ConditionNodeDataModel> ChildConditionNodes { get; set; }
8+
[BsonElement(Order = 1)]
9+
public ConditionNodeDataModel[] ChildConditionNodes { get; set; }
1110
}
12-
}
11+
}

src/Rules.Framework.Providers.MongoDb/DataModel/ConditionNodeDataModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ public ConditionNodeDataModel()
1515
this.Properties = new Dictionary<string, object>(StringComparer.Ordinal);
1616
}
1717

18+
[BsonElement(Order = 1)]
1819
[BsonRepresentation(BsonType.String)]
1920
public LogicalOperators LogicalOperator { get; set; }
2021

22+
[BsonElement(Order = 2)]
2123
[BsonDictionaryOptions(Representation = DictionaryRepresentation.Document)]
2224
public IDictionary<string, object> Properties { get; set; }
2325
}

src/Rules.Framework.Providers.MongoDb/DataModel/RuleDataModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@ namespace Rules.Framework.Providers.MongoDb.DataModel
55

66
internal sealed class RuleDataModel
77
{
8+
[BsonElement(Order = 8)]
89
public bool? Active { get; set; }
10+
11+
[BsonElement(Order = 7)]
912
public dynamic Content { get; set; }
1013

14+
[BsonElement(Order = 2)]
1115
public string ContentType { get; set; }
1216

17+
[BsonElement(Order = 3)]
1318
public DateTime DateBegin { get; set; }
1419

20+
[BsonElement(Order = 4)]
1521
public DateTime? DateEnd { get; set; }
1622

17-
[BsonId]
23+
[BsonId(Order = 1)]
1824
public string Name { get; set; }
1925

26+
[BsonElement(Order = 5)]
2027
public int Priority { get; set; }
2128

29+
[BsonElement(Order = 6)]
2230
public ConditionNodeDataModel RootCondition { get; set; }
2331
}
2432
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,16 @@ private async Task<IEnumerable<Rule<TContentType, TConditionType>>> GetRulesAsyn
155155

156156
var fetchedRules = await fetchedRulesCursor.ToListAsync().ConfigureAwait(false);
157157

158-
return fetchedRules.Select(r => this.ruleFactory.CreateRule(r));
158+
// We won't use LINQ from this point onwards to avoid projected queries to database at a
159+
// later point. This approach assures the definitive realization of the query results
160+
// and does not produce side effects later on.
161+
var result = new Rule<TContentType, TConditionType>[fetchedRules.Count];
162+
for (int i = 0; i < result.Length; i++)
163+
{
164+
result[i] = this.ruleFactory.CreateRule(fetchedRules[i]);
165+
}
166+
167+
return result;
159168
}
160169
}
161170
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
namespace Rules.Framework.Providers.MongoDb
2+
{
3+
using System;
4+
using System.Globalization;
5+
using System.Threading.Tasks;
6+
using MongoDB.Driver;
7+
using Rules.Framework.Providers.MongoDb.DataModel;
8+
9+
internal static class MongoDbRulesDataSourceInitializer
10+
{
11+
private static bool isInitialized;
12+
13+
public static async Task InitializeAsync(IMongoClient mongoClient, MongoDbProviderSettings mongoDbProviderSettings)
14+
{
15+
if (isInitialized)
16+
{
17+
return;
18+
}
19+
20+
if (mongoClient is null)
21+
{
22+
throw new ArgumentNullException(nameof(mongoClient));
23+
}
24+
25+
if (mongoDbProviderSettings is null)
26+
{
27+
throw new ArgumentNullException(nameof(mongoDbProviderSettings));
28+
}
29+
30+
var mongoDatabase = mongoClient.GetDatabase(mongoDbProviderSettings.DatabaseName, new MongoDatabaseSettings
31+
{
32+
ReadConcern = ReadConcern.Available,
33+
ReadPreference = ReadPreference.SecondaryPreferred,
34+
WriteConcern = WriteConcern.Acknowledged,
35+
});
36+
37+
await CreateRulesCollectionIfNotExists(mongoDatabase, mongoDbProviderSettings).ConfigureAwait(false);
38+
39+
var rulesCollection = mongoDatabase.GetCollection<RuleDataModel>(mongoDbProviderSettings.RulesCollectionName);
40+
41+
await CreateIndexesIfNotExists(rulesCollection, mongoDbProviderSettings).ConfigureAwait(false);
42+
43+
// Mark as initialized as this initialization code is never run anymore throughout the
44+
// app lifecycle.
45+
isInitialized = true;
46+
}
47+
48+
private static async Task CreateIndexesIfNotExists(IMongoCollection<RuleDataModel> rulesCollection, MongoDbProviderSettings mongoDbProviderSettings)
49+
{
50+
var getRulesIndex = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_get_rules";
51+
var getRulesIndexKeysDefinition = Builders<RuleDataModel>.IndexKeys
52+
.Ascending("ContentType").Ascending("DateBegin").Ascending("DateEnd");
53+
await CreateIndexOnBackgroundAsync(rulesCollection, getRulesIndex, getRulesIndexKeysDefinition).ConfigureAwait(false);
54+
var getRulesByIndex = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_get_rules_by";
55+
var getRulesByIndexKeysDefinition = Builders<RuleDataModel>.IndexKeys
56+
.Ascending("ContentType").Ascending("Name").Ascending("Priority");
57+
await CreateIndexOnBackgroundAsync(rulesCollection, getRulesByIndex, getRulesByIndexKeysDefinition).ConfigureAwait(false);
58+
}
59+
60+
private static async Task CreateIndexOnBackgroundAsync<T>(IMongoCollection<T> mongoCollection, string indexName, IndexKeysDefinition<T> indexKeysDefinition)
61+
{
62+
var createIndexOptions = new CreateIndexOptions
63+
{
64+
Background = true,
65+
Name = indexName,
66+
};
67+
var createIndexModel = new CreateIndexModel<T>(indexKeysDefinition, createIndexOptions);
68+
_ = await mongoCollection.Indexes.CreateOneAsync(createIndexModel).ConfigureAwait(false);
69+
}
70+
71+
private static async Task CreateRulesCollectionIfNotExists(IMongoDatabase mongoDatabase, MongoDbProviderSettings mongoDbProviderSettings)
72+
{
73+
var collectionsCursor = await mongoDatabase.ListCollectionNamesAsync().ConfigureAwait(false);
74+
var collections = await collectionsCursor.ToListAsync().ConfigureAwait(false);
75+
if (!collections.Contains(mongoDbProviderSettings.RulesCollectionName))
76+
{
77+
await mongoDatabase.CreateCollectionAsync(mongoDbProviderSettings.RulesCollectionName).ConfigureAwait(false);
78+
}
79+
}
80+
}
81+
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ public static class MongoDbRulesDataSourceSelectorExtensions
2121
/// <param name="mongoDbProviderSettings">The mongo database provider settings.</param>
2222
/// <returns></returns>
2323
/// <exception cref="ArgumentNullException">
24-
/// rulesDataSourceSelector
25-
/// or
26-
/// mongoClient
27-
/// or
28-
/// mongoDbProviderSettings
24+
/// rulesDataSourceSelector or mongoClient or mongoDbProviderSettings
2925
/// </exception>
3026
public static IConfiguredRulesEngineBuilder<TContentType, TConditionType> SetMongoDbDataSource<TContentType, TConditionType>(
3127
this IRulesDataSourceSelector<TContentType, TConditionType> rulesDataSourceSelector,
@@ -47,6 +43,7 @@ public static IConfiguredRulesEngineBuilder<TContentType, TConditionType> SetMon
4743
throw new ArgumentNullException(nameof(mongoDbProviderSettings));
4844
}
4945

46+
MongoDbRulesDataSourceInitializer.InitializeAsync(mongoClient, mongoDbProviderSettings).GetAwaiter().GetResult();
5047
IContentSerializationProvider<TContentType> contentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider<TContentType>();
5148
IRuleFactory<TContentType, TConditionType> ruleFactory = new RuleFactory<TContentType, TConditionType>(contentSerializationProvider);
5249
MongoDbProviderRulesDataSource<TContentType, TConditionType> mongoDbProviderRulesDataSource

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

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ public RuleDataModel CreateRule(Rule<TContentType, TConditionType> rule)
6060
throw new ArgumentNullException(nameof(rule));
6161
}
6262

63-
var content = rule.ContentContainer.GetContentAs<dynamic>();
63+
var content = rule.ContentContainer.GetContentAs<object>();
64+
var serializedContent = this.contentSerializationProvider.GetContentSerializer(rule.ContentContainer.ContentType).Serialize(content);
6465

6566
var ruleDataModel = new RuleDataModel
6667
{
67-
Content = content,
68+
Content = serializedContent,
6869
ContentType = Convert.ToString(rule.ContentContainer.ContentType, CultureInfo.InvariantCulture),
6970
DateBegin = rule.DateBegin,
7071
DateEnd = rule.DateEnd,
@@ -89,10 +90,12 @@ private static IConditionNode<TConditionType> ConvertConditionNode(
8990

9091
var composedConditionNodeBuilder = conditionNodeBuilder.AsComposed()
9192
.WithLogicalOperator(composedConditionNodeDataModel.LogicalOperator);
92-
93-
foreach (ConditionNodeDataModel child in composedConditionNodeDataModel.ChildConditionNodes)
93+
var childConditionNodes = composedConditionNodeDataModel.ChildConditionNodes;
94+
var count = childConditionNodes.Length;
95+
var i = -1;
96+
while (++i < count)
9497
{
95-
composedConditionNodeBuilder.AddCondition(cnb => ConvertConditionNode(cnb, child));
98+
composedConditionNodeBuilder.AddCondition(cnb => ConvertConditionNode(cnb, childConditionNodes[i]));
9699
}
97100

98101
var composedConditionNode = composedConditionNodeBuilder.Build();
@@ -104,15 +107,20 @@ private static IConditionNode<TConditionType> ConvertConditionNode(
104107
return composedConditionNode;
105108
}
106109

107-
private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode<TConditionType> valueConditionNode) => new ValueConditionNodeDataModel
110+
private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode<TConditionType> valueConditionNode)
108111
{
109-
ConditionType = Convert.ToString(valueConditionNode.ConditionType, CultureInfo.InvariantCulture),
110-
LogicalOperator = LogicalOperators.Eval,
111-
DataType = valueConditionNode.DataType,
112-
Operand = valueConditionNode.Operand,
113-
Operator = valueConditionNode.Operator,
114-
Properties = valueConditionNode.Properties.ToDictionary(x => x.Key, x => x.Value, StringComparer.Ordinal),
115-
};
112+
var properties = FilterProperties(valueConditionNode.Properties);
113+
114+
return new ValueConditionNodeDataModel
115+
{
116+
ConditionType = Convert.ToString(valueConditionNode.ConditionType, CultureInfo.InvariantCulture),
117+
LogicalOperator = LogicalOperators.Eval,
118+
DataType = valueConditionNode.DataType,
119+
Operand = valueConditionNode.Operand,
120+
Operator = valueConditionNode.Operator,
121+
Properties = properties,
122+
};
123+
}
116124

117125
private static IConditionNode<TConditionType> CreateValueConditionNode(IConditionNodeBuilder<TConditionType> conditionNodeBuilder, ValueConditionNodeDataModel conditionNodeDataModel)
118126
{
@@ -125,19 +133,19 @@ private static IConditionNode<TConditionType> CreateValueConditionNode(IConditio
125133
.SetOperand(Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
126134
.Build(),
127135
DataTypes.Decimal => conditionNodeBuilder.AsValued(conditionType)
128-
.OfDataType<decimal>()
129-
.WithComparisonOperator(conditionNodeDataModel.Operator)
130-
.SetOperand(Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
131-
.Build(),
136+
.OfDataType<decimal>()
137+
.WithComparisonOperator(conditionNodeDataModel.Operator)
138+
.SetOperand(Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
139+
.Build(),
132140
DataTypes.String => conditionNodeBuilder.AsValued(conditionType)
133-
.OfDataType<string>()
134-
.WithComparisonOperator(conditionNodeDataModel.Operator)
135-
.SetOperand(Convert.ToString(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
136-
.Build(),
141+
.OfDataType<string>()
142+
.WithComparisonOperator(conditionNodeDataModel.Operator)
143+
.SetOperand(Convert.ToString(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
144+
.Build(),
137145
DataTypes.Boolean => conditionNodeBuilder.AsValued(conditionType)
138-
.OfDataType<bool>()
139-
.WithComparisonOperator(conditionNodeDataModel.Operator)
140-
.SetOperand(Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
146+
.OfDataType<bool>()
147+
.WithComparisonOperator(conditionNodeDataModel.Operator)
148+
.SetOperand(Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture))
141149
.Build(),
142150

143151
DataTypes.ArrayInteger => conditionNodeBuilder.AsValued(conditionType)
@@ -171,6 +179,22 @@ private static IConditionNode<TConditionType> CreateValueConditionNode(IConditio
171179
return valueConditionNode;
172180
}
173181

182+
private static IDictionary<string, object> FilterProperties(IDictionary<string, object> source)
183+
{
184+
var properties = new Dictionary<string, object>(StringComparer.Ordinal);
185+
foreach (var property in source)
186+
{
187+
if (property.Key.StartsWith("_compilation", StringComparison.Ordinal))
188+
{
189+
continue;
190+
}
191+
192+
properties[property.Key] = property.Value;
193+
}
194+
195+
return properties;
196+
}
197+
174198
private static T Parse<T>(string value)
175199
=> (T)Parse(value, typeof(T));
176200

@@ -179,18 +203,20 @@ private static object Parse(string value, Type type)
179203

180204
private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNode<TConditionType> composedConditionNode)
181205
{
182-
var conditionNodeDataModels = new List<ConditionNodeDataModel>(composedConditionNode.ChildConditionNodes.Count());
183-
206+
var conditionNodeDataModels = new ConditionNodeDataModel[composedConditionNode.ChildConditionNodes.Count()];
207+
var i = 0;
184208
foreach (var child in composedConditionNode.ChildConditionNodes)
185209
{
186-
conditionNodeDataModels.Add(this.ConvertConditionNode(child));
210+
conditionNodeDataModels[i++] = this.ConvertConditionNode(child);
187211
}
188212

213+
var properties = FilterProperties(composedConditionNode.Properties);
214+
189215
return new ComposedConditionNodeDataModel
190216
{
191217
ChildConditionNodes = conditionNodeDataModels,
192218
LogicalOperator = composedConditionNode.LogicalOperator,
193-
Properties = composedConditionNode.Properties.ToDictionary(x => x.Key, x => x.Value, StringComparer.Ordinal),
219+
Properties = properties,
194220
};
195221
}
196222

src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
5-
<LangVersion>9.0</LangVersion>
5+
<LangVersion>10.0</LangVersion>
66
<GenerateDocumentationFile>true</GenerateDocumentationFile>
77
<Authors></Authors>
88
<Version></Version>

src/Rules.Framework.Providers.MongoDb/Serialization/DynamicToStrongTypeContentSerializationProvider.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,27 @@ namespace Rules.Framework.Providers.MongoDb.Serialization
77
/// Defines a content serialization provider for dynamic types.
88
/// </summary>
99
/// <typeparam name="TContentType">The type of the content type.</typeparam>
10-
/// <seealso cref="Rules.Framework.Serialization.IContentSerializationProvider{TContentType}" />
10+
/// <seealso cref="Rules.Framework.Serialization.IContentSerializationProvider{TContentType}"/>
1111
public class DynamicToStrongTypeContentSerializationProvider<TContentType> : IContentSerializationProvider<TContentType>
1212
{
1313
private readonly Lazy<IContentSerializer> contentSerializerLazy;
1414

1515
/// <summary>
16-
/// Initializes a new instance of the <see cref="DynamicToStrongTypeContentSerializationProvider{TContentType}"/> class.
16+
/// Initializes a new instance of the <see
17+
/// cref="DynamicToStrongTypeContentSerializationProvider{TContentType}"/> class.
1718
/// </summary>
1819
public DynamicToStrongTypeContentSerializationProvider()
1920
{
2021
this.contentSerializerLazy = new Lazy<IContentSerializer>(
2122
() => new DynamicToStrongTypeContentSerializer(),
22-
System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
23+
System.Threading.LazyThreadSafetyMode.PublicationOnly);
2324
}
2425

2526
/// <summary>
26-
/// Gets the content serializer associated with the given <paramref name="contentType" />.
27+
/// Gets the content serializer associated with the given <paramref name="contentType"/>.
2728
/// </summary>
2829
/// <param name="contentType">the content type.</param>
29-
/// <returns>
30-
/// the content serializer to deal with contents for specified content type.
31-
/// </returns>
30+
/// <returns>the content serializer to deal with contents for specified content type.</returns>
3231
public IContentSerializer GetContentSerializer(TContentType contentType) => this.contentSerializerLazy.Value;
3332
}
3433
}

0 commit comments

Comments
 (0)