Skip to content

Commit

Permalink
Merge pull request #387 from indice-co/feature/risk/db-config
Browse files Browse the repository at this point in the history
Add IAppSettingsDbContext in RiskDbContext
  • Loading branch information
ar-is authored Mar 4, 2024
2 parents a144048 + 24b7f62 commit 12de38a
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<VersionPrefixCases>$(VersionPrefixBase).2</VersionPrefixCases>
<VersionPrefixMessages>$(VersionPrefixBase).1</VersionPrefixMessages>
<VersionPrefixMultitenancy>$(VersionPrefixBase).0</VersionPrefixMultitenancy>
<VersionPrefixRisk>$(VersionPrefixBase).1</VersionPrefixRisk>
<VersionPrefixRisk>$(VersionPrefixBase).2</VersionPrefixRisk>
<VersionPrefixMedia>$(VersionPrefixBase).0</VersionPrefixMedia>
<VersionPrefix>$(VersionPrefixBase).0</VersionPrefix>
<!--<VersionSuffix>beta-01</VersionSuffix>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ public static class DatabaseConfigurationExtensions
/// <param name="configureAction">The <see cref="EntityConfigurationOptions"/> to use.</param>
/// <returns>The <see cref="IHostBuilder"/>.</returns>
public static IWebHostBuilder AddDatabaseSettings<TContext>(this IWebHostBuilder webHostBuilder, Action<EntityConfigurationOptions, IConfiguration> configureAction) where TContext : DbContext, IAppSettingsDbContext =>
webHostBuilder.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder) => {
var options = new EntityConfigurationOptions();
configureAction?.Invoke(options, configurationBuilder.Build());
var result = options.Validate();
if (!result.Succedded) {
throw new ArgumentException(result.Error);
}
configurationBuilder.Add(new EntityConfigurationSource<TContext>(options));
})
.ConfigureServices((context, services) => {
services.AddTransient<IAppSettingsDbContext, TContext>();
});
webHostBuilder.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder) => {
var options = new EntityConfigurationOptions();
configureAction?.Invoke(options, configurationBuilder.Build());
var result = options.Validate();
if (!result.Succedded) {
throw new ArgumentException(result.Error);
}
configurationBuilder.Add(new EntityConfigurationSource<TContext>(options));
})
.ConfigureServices((context, services) => {
services.AddTransient<IAppSettingsDbContext, TContext>();
});
}
7 changes: 5 additions & 2 deletions src/Indice.Features.Risk.Core/Abstractions/RiskRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ public RiskRule(string ruleName) {

/// <summary>The name of the rule.</summary>
public string Name { get; }


/// <summary>Whether the rule is enabled or not.</summary>
public bool Enabled { get; set; } = true;

/// <summary>Executes the rule asynchronously.</summary>
/// <param name="event">The event occurred.</param>
/// <returns>The result of rule execution.</returns>
public abstract ValueTask<RuleExecutionResult> ExecuteAsync(RiskEvent @event);
}
}
21 changes: 15 additions & 6 deletions src/Indice.Features.Risk.Core/Configuration/RiskEngineBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Indice.Features.Risk.Core.Abstractions;
using Indice.Features.Risk.Core.Data;
using Indice.Features.Risk.Core.Data.Models;
using Indice.Features.Risk.Core.Models;
using Indice.Features.Risk.Core.Rules;
using Indice.Features.Risk.Core.Stores;
using Microsoft.EntityFrameworkCore;
Expand All @@ -10,8 +11,9 @@ namespace Indice.Features.Risk.Core.Configuration;

/// <summary>Builder class used to configure the risk engine feature.</summary>
public class RiskEngineBuilder

{
private const string RULE_OPTIONS_SECTION = "RuleOptions";

private readonly List<string> _ruleNames = new();
private readonly IServiceCollection _services;

Expand All @@ -21,35 +23,42 @@ internal RiskEngineBuilder(IServiceCollection services) {

/// <summary>Adds a new rule to the risk engine by providing an implementation of <see cref="RiskRule"/>.</summary>
/// <typeparam name="TRule">The implementation type.</typeparam>
/// <typeparam name="TOptions">The options associated with the rule.</typeparam>
/// <param name="name">The name of the rule.</param>
/// <returns>The instance of <see cref="RiskEngineBuilder"/>.</returns>
public RiskEngineBuilder AddRule<TRule>(string name) where TRule : RiskRule {
public RiskEngineBuilder AddRule<TRule, TOptions>(string name)
where TRule : RiskRule
where TOptions : RuleOptionsBase, new() {
CheckAndAddRuleName(name);
_services.AddOptions<TOptions>().BindConfiguration($"{RULE_OPTIONS_SECTION}:{name}");
_services.AddTransient<RiskRule, TRule>();
return this;
}

/// <summary>Adds a new rule to the risk engine by providing a lambda expression.</summary>
/// <typeparam name="TOptions">The options associated with the rule.</typeparam>
/// <param name="name">The name of the rule.</param>
/// <param name="ruleDelegate">The delegate method to when a new event occurs.</param>
/// <returns>The instance of <see cref="RiskEngineBuilder"/>.</returns>
public RiskEngineBuilder AddRule(
public RiskEngineBuilder AddRule<TOptions>(
string name,
Func<IServiceProvider, RiskEvent, ValueTask<RuleExecutionResult>> ruleDelegate
) {
) where TOptions : RuleOptionsBase, new() {
CheckAndAddRuleName(name);
_services.AddOptions<TOptions>().BindConfiguration($"{RULE_OPTIONS_SECTION}:{name}");
_services.AddTransient<RiskRule>(serviceProvider => new GenericRule(name, serviceProvider, ruleDelegate));
return this;
}

/// <summary>Adds a new rule to the risk engine by providing a lambda expression.</summary>
/// <typeparam name="TOptions">The options associated with the rule.</typeparam>
/// <param name="name">The name of the rule.</param>
/// <param name="ruleDelegate">The delegate method to when a new event occurs.</param>
/// <returns>The instance of <see cref="RiskEngineBuilder"/>.</returns>
public RiskEngineBuilder AddRule(
public RiskEngineBuilder AddRule<TOptions>(
string name,
Func<RiskEvent, ValueTask<RuleExecutionResult>> ruleDelegate
) => AddRule(name, (serviceProvider, @event) => ruleDelegate(@event));
) where TOptions : RuleOptionsBase, new() => AddRule<TOptions>(name, (serviceProvider, @event) => ruleDelegate(@event));

/// <summary>Registers an implementation of <see cref="IRiskEventStore"/> where Entity Framework Core is used as a persistent mechanism.</summary>
/// <param name="dbContextOptionsBuilderAction">The builder being used to configure the context.</param>
Expand Down
12 changes: 11 additions & 1 deletion src/Indice.Features.Risk.Core/Data/RiskDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using Indice.Configuration;
using Indice.EntityFrameworkCore;
using Indice.Extensions.Configuration.Database;
using Indice.Extensions.Configuration.Database.Data;
using Indice.Extensions.Configuration.Database.Data.Models;
using Indice.Features.Risk.Core.Data.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Indice.Features.Risk.Core.Data;

/// <summary>A <see cref="DbContext"/> for persisting events and their related data.</summary>
public class RiskDbContext : DbContext
public class RiskDbContext : DbContext, IAppSettingsDbContext
{
/// <summary>Creates a new instance of <see cref="RiskDbContext"/> class.</summary>
/// <param name="dbContextOptions"></param>
Expand All @@ -20,6 +23,11 @@ public RiskDbContext(DbContextOptions<RiskDbContext> dbContextOptions) : base(db
/// <summary>Risk results table.</summary>
public DbSet<DbAggregateRuleExecutionResult> RiskResults => Set<DbAggregateRuleExecutionResult>();

/// <summary>
/// Risk rules definitions table.
/// </summary>
public DbSet<DbAppSetting> AppSettings { get; set; }

/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
Expand Down Expand Up @@ -51,5 +59,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<DbAggregateRuleExecutionResult>().Property(x => x.RiskScore).IsRequired();
modelBuilder.Entity<DbAggregateRuleExecutionResult>().Property(x => x.RiskLevel).HasMaxLength(TextSizePresets.S64).IsRequired();
modelBuilder.ApplyJsonFunctions();
// Risk rules definitions configuration.
modelBuilder.ApplyConfiguration(new AppSettingMap());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@
<ItemGroup>
<PackageReference Include="Indice.Common" Version="$(VersionPrefixCore)" />
<PackageReference Include="Indice.EntityFrameworkCore" Version="$(VersionPrefixCore)" />
<PackageReference Include="Indice.Extensions.Configuration.Database" Version="$(VersionPrefixCore)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.16"></PackageReference>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0"></PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5"></PackageReference>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0"></PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0"></PackageReference>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0"></PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Indice.Common\Indice.Common.csproj" />
<ProjectReference Include="..\Indice.EntityFrameworkCore\Indice.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\Indice.Extensions.Configuration.Database\Indice.Extensions.Configuration.Database.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Indice.Features.Risk.Server" />
Expand Down
22 changes: 22 additions & 0 deletions src/Indice.Features.Risk.Core/Models/RuleOptionsBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Indice.Features.Risk.Core.Models;

Check warning on line 1 in src/Indice.Features.Risk.Core/Models/RuleOptionsBase.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [D:\a\Indice.AspNet\Indice.AspNet\src\Indice.Features.Risk.Core\Indice.Features.Risk.Core.csproj::TargetFramework=net8.0]

Check warning on line 1 in src/Indice.Features.Risk.Core/Models/RuleOptionsBase.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [D:\a\Indice.AspNet\Indice.AspNet\src\Indice.Features.Risk.Core\Indice.Features.Risk.Core.csproj::TargetFramework=net8.0]

/// <summary>
/// Contains the basic options for each rule registered in the Risk Engine.
/// </summary>
public class RuleOptionsBase
{
/// <summary>
/// Name of the rule.
/// </summary>
public string Name { get; set; }

/// <summary>
/// A description of the rule.
/// </summary>
public string Description { get; set; }

/// <summary>
/// Whether the rule is enabled or not.
/// </summary>
public bool Enabled { get; set; }
}
2 changes: 1 addition & 1 deletion src/Indice.Features.Risk.Core/Services/RiskManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public Task<IEnumerable<RiskEvent>> GetRiskEventsAsync(string subjectId, string[
/// <param name="event">The event occurred for which to calculate the risk score.</param>
public async Task<AggregateRuleExecutionResult> GetRiskAsync(RiskEvent @event) {
var results = new List<RuleExecutionResult>();
foreach (var rule in Rules) {
foreach (var rule in Rules.Where(x => x.Enabled)) {
var result = await rule.ExecuteAsync(@event);
result.RuleName = rule.Name;
result.RiskLevel = RiskEngineOptions.RiskLevelRangeMapping.GetRiskLevel(result.RiskScore) ?? RiskLevel.None;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public async Task CreateAsync(DbAggregateRuleExecutionResult riskResult) {
public async Task AddEventIdAsync(Guid resultId, Guid eventId) {
var riskResult = await _dbContext.RiskResults.FindAsync(resultId) ?? throw new Exception("Risk Result not found.");
riskResult.EventId = eventId;
await _dbContext.SaveChangesAsync();
await _dbContext.SaveChangesAsync();
}

public async Task<ResultSet<DbAggregateRuleExecutionResult>> GetList(ListOptions<AdminRiskFilter> options) {
Expand Down
2 changes: 1 addition & 1 deletion src/Indice.Features.Risk.Core/Types/IntegerRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public int UpperLimit {

/// <summary>Check if the current range is overlapped by another given range.</summary>
/// <param name="other">The range to compare to.</param>
public bool IsOverlapped(IntegerRange other) =>
public bool IsOverlapped(IntegerRange other) =>
LowerLimit.CompareTo(other.UpperLimit) < 0 && other.LowerLimit.CompareTo(UpperLimit) < 0;

/// <inheritdoc />
Expand Down
3 changes: 2 additions & 1 deletion test/Indice.Features.Risk.Tests/RiskCalculationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Indice.Features.Risk.Core.Data.Models;
using Indice.Features.Risk.Core.Enums;
using Indice.Features.Risk.Core.Extensions;
using Indice.Features.Risk.Core.Models;
using Indice.Features.Risk.Core.Services;
using Indice.Features.Risk.Core.Types;
using Microsoft.EntityFrameworkCore;
Expand All @@ -24,7 +25,7 @@ public RiskCalculationTests() {
[RiskLevel.High] = new IntegerRange(2001, 3000)
});
})
.AddRule("TransactionOver1000", riskEvent =>
.AddRule<RuleOptionsBase>("TransactionOver1000", riskEvent =>
ValueTask.FromResult(
riskEvent.Type == "Transaction" && riskEvent.Amount >= 1000
? RuleExecutionResult.HighRisk()
Expand Down

0 comments on commit 12de38a

Please sign in to comment.