configure Redis cache and distributed locks

This commit is contained in:
Behzad Alizadeh
2026-10-10 16:51:15 +03:30
parent f4dba6874c
commit 18e91d0b18
9 changed files with 2465 additions and 64 deletions
@@ -1,6 +1,9 @@
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Services;
using Elsa.Caching.Options;
using Elsa.Caching.Features;
using Microsoft.Extensions.Configuration;
using Caspian.WorkflowFramework.Config;
using Elsa.Common.RecurringTasks;
using Elsa.Common.Serialization;
using Elsa.EntityFrameworkCore.Extensions;
@@ -36,6 +39,10 @@ namespace Caspian.WorkflowFramework;
public static class DependencyInjection
{
public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string sqlServerConnectionString)
=> builder.AddWorkflowFramework(sqlServerConnectionString, null);
public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string sqlServerConnectionString,
Action<CacheFeature>? configureCache)
{
//var configuration = new ConfigurationBuilder()
// .AddJsonFile(appsettingName, optional: false, reloadOnChange: true)
@@ -45,7 +52,7 @@ public static class DependencyInjection
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>("SqlServer");
const bool useDbContextPooling = false;
const bool runEFCoreMigrations = true;
const bool useCaching = true;
const bool useReadOnlyMode = false;
const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed;
const bool disableVariableWrappers = false;
@@ -63,6 +70,13 @@ public static class DependencyInjection
builder.Services.AddElsa(elsa =>
{
elsa
.UseCaching(cache =>
{
cache.Duration = TimeSpan.FromDays(1);
cache.RegisterRedis();
cache.UseConfiguration(builder.Configuration.GetSection("Workflow:Cache"));
configureCache?.Invoke(cache);
})
.UseWorkflows(workflows =>
{
workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing());
@@ -81,8 +95,7 @@ public static class DependencyInjection
ef.UseSqlServer(sqlServerConnectionString);
});
if (useCaching)
management.UseCache();
management.UseCache();
management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit);
management.UseReadOnlyMode(useReadOnlyMode);
@@ -106,8 +119,8 @@ public static class DependencyInjection
runtime.UseDistributedRuntime();
}
if (useCaching)
runtime.UseCache();
runtime.UseCache();
runtime.UseConfiguredDistributedLocking(builder.Configuration);
})
.UseCSharp(options =>
{
@@ -128,7 +141,7 @@ public static class DependencyInjection
//});
})
// برای ثبت Activityهایی مانند Elsa.SendHttpRequest
.UseHttp()
.UseHttp(http => http.UseCache())
.UseWorkflowContexts();
});
@@ -143,7 +156,7 @@ public static class DependencyInjection
builder.Services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
builder.Services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
builder.Services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
builder.Services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
//builder.Services.AddHealthChecks();
@@ -0,0 +1,8 @@
using Medallion.Threading;
namespace Caspian.WorkflowFramework.Config;
internal class PrefixedDistributedLockProvider(IDistributedLockProvider provider, string prefix) : IDistributedLockProvider
{
public IDistributedLock CreateLock(string name) => provider.CreateLock(prefix + name);
}
@@ -0,0 +1,44 @@
using Elsa.Workflows.Runtime.Features;
using Medallion.Threading.Redis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
namespace Caspian.WorkflowFramework.Config;
internal static class WorkflowRuntimeFeatureExtensions
{
internal static void UseConfiguredDistributedLocking(this WorkflowRuntimeFeature runtime, IConfiguration configuration)
{
var legacySettings = configuration.GetSection("Runtime:DistributedLocking");
var settings = configuration.GetSection("Workflow:Runtime:DistributedLocking");
runtime.DistributedLockingOptions = options =>
{
legacySettings.Bind(options);
settings.Bind(options);
};
var providerName = settings["Provider"] ?? legacySettings["Provider"];
switch (providerName?.ToUpperInvariant())
{
case "REDIS":
{
var database = settings.GetValue<int?>("Database")
?? legacySettings.GetValue<int?>("Database")
?? configuration.GetValue<int?>("Redis:Database") ?? -1;
var prefix = settings["KeyPrefix"] ?? legacySettings["KeyPrefix"] ?? "nextbank:workflow:locks:";
if (database < -1) throw new InvalidOperationException("Workflow lock database must be -1 or a non-negative number.");
if (string.IsNullOrWhiteSpace(prefix)) throw new InvalidOperationException("Workflow lock KeyPrefix must not be empty.");
runtime.DistributedLockProvider = sp => new PrefixedDistributedLockProvider(
new RedisDistributedSynchronizationProvider(sp.GetRequiredService<IConnectionMultiplexer>().GetDatabase(database)), prefix);
break;
}
case null:
case "":
case "FILE":
break;
default:
throw new InvalidOperationException($"Unsupported workflow lock provider '{providerName}'. Configure File or Redis.");
}
}
}