Add default implementations for job queue and scheduler
This commit is contained in:
parent
b566cad1f2
commit
e942f799ec
23
Elsa.sln
23
Elsa.sln
|
|
@ -64,8 +64,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa", "src\bundles\Elsa\El
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Formatting", "src\common\Elsa.Formatting\Elsa.Formatting.csproj", "{60CEE728-3187-481B-B6EA-10D5284D8B55}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Jobs.Abstractions", "src\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj", "{2782C593-98CD-4B80-B595-8BAE066DCBC3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.ServiceBus.Abstractions", "src\common\Elsa.ServiceBus.Abstractions\Elsa.ServiceBus.Abstractions.csproj", "{448FCDE6-DE44-4E44-A1CC-B9AA51471121}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Liquid", "src\modules\Elsa.Liquid\Elsa.Liquid.csproj", "{8E5BD3D9-3529-48BC-B741-CFB7A69DB233}"
|
||||
|
|
@ -118,7 +116,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Api.Common", "src\comm
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Tokens.Api", "src\modules\Elsa.Tokens.Api\Elsa.Tokens.Api.csproj", "{30B49E42-AE61-4F7C-981F-923F07BB3986}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Jobs", "src\modules\Elsa.Activities.Jobs\Elsa.Activities.Jobs.csproj", "{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Jobs.Activities", "src\modules\Elsa.Jobs.Activities\Elsa.Jobs.Activities.csproj", "{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Jobs", "src\modules\Elsa.Jobs\Elsa.Jobs.csproj", "{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Common", "src\modules\Elsa.Common\Elsa.Common.csproj", "{D229105F-6879-4452-9189-75DE060C0F4C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -206,10 +208,6 @@ Global
|
|||
{60CEE728-3187-481B-B6EA-10D5284D8B55}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{60CEE728-3187-481B-B6EA-10D5284D8B55}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{60CEE728-3187-481B-B6EA-10D5284D8B55}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2782C593-98CD-4B80-B595-8BAE066DCBC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2782C593-98CD-4B80-B595-8BAE066DCBC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2782C593-98CD-4B80-B595-8BAE066DCBC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2782C593-98CD-4B80-B595-8BAE066DCBC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{448FCDE6-DE44-4E44-A1CC-B9AA51471121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{448FCDE6-DE44-4E44-A1CC-B9AA51471121}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{448FCDE6-DE44-4E44-A1CC-B9AA51471121}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
@ -318,6 +316,14 @@ Global
|
|||
{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -335,7 +341,6 @@ Global
|
|||
{177F4FF3-4618-4CB1-BA93-8B024BC64CD8} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{F06B9573-DF68-4606-866C-A7546A10A05A} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
{60CEE728-3187-481B-B6EA-10D5284D8B55} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{2782C593-98CD-4B80-B595-8BAE066DCBC3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{448FCDE6-DE44-4E44-A1CC-B9AA51471121} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{B2049499-D384-46DF-8837-F1180107DD54} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{FC5D2CE9-FFB0-478D-8D54-E4109D1FD202} = {F06B9573-DF68-4606-866C-A7546A10A05A}
|
||||
|
|
@ -374,5 +379,7 @@ Global
|
|||
{39CD855E-83B1-4A96-93F7-01608211EBE3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{30B49E42-AE61-4F7C-981F-923F07BB3986} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{81A2AE6B-3D0C-4A4A-A80B-7A661FBC8117} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{CF1938A0-BD10-4AA1-9ABC-6D02303A9D53} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{D229105F-6879-4452-9189-75DE060C0F4C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\bundles\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.AspNetCore\Elsa.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ActivityDefinitions\Elsa.ActivityDefinitions.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Jobs\Elsa.Jobs.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Api.Extensions;
|
||||
using Elsa.AspNetCore.Extensions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Jobs.Extensions;
|
||||
using Elsa.Hangfire.Implementations;
|
||||
using Elsa.Http;
|
||||
using Elsa.Http.Extensions;
|
||||
using Elsa.JavaScript.Activities;
|
||||
using Elsa.JavaScript.Extensions;
|
||||
using Elsa.Quartz.Implementations;
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Scheduling.Extensions;
|
||||
using Elsa.Liquid.Extensions;
|
||||
using Elsa.Scheduling.Extensions;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
|
|
@ -43,6 +40,8 @@ services
|
|||
.AddActivity<Switch>()
|
||||
.AddActivity<RunJavaScript>()
|
||||
)
|
||||
.UseJobs()
|
||||
.UseScheduling()
|
||||
.UseJavaScript()
|
||||
.UseLiquid()
|
||||
.UseHttp()
|
||||
|
|
@ -50,10 +49,6 @@ services
|
|||
.UseMvc()
|
||||
);
|
||||
|
||||
services
|
||||
.AddJobServices(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
|
||||
.AddSchedulingServices();
|
||||
|
||||
// Register serialization configurator for configuring what types to allow to be serialized.
|
||||
services.AddSingleton<ISerializationOptionsConfigurator, CustomSerializationOptionConfigurator>();
|
||||
services.AddSingleton<ISerializationOptionsConfigurator, SerializationOptionsConfigurator>();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Activities.Jobs\Elsa.Activities.Jobs.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Jobs.Activities\Elsa.Jobs.Activities.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ActivityDefinitions.EntityFrameworkCore.Sqlite\Elsa.ActivityDefinitions.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ActivityDefinitions.EntityFrameworkCore\Elsa.ActivityDefinitions.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ActivityDefinitions\Elsa.ActivityDefinitions.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Elsa.Activities.Jobs.Features;
|
||||
using Elsa.Jobs.Abstractions;
|
||||
using Elsa.Jobs.Activities.Features;
|
||||
using Elsa.Jobs.Models;
|
||||
using Elsa.Jobs.Services;
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ namespace Elsa.WorkflowServer.Web.Jobs;
|
|||
|
||||
/// <summary>
|
||||
/// Jobs can be scheduled manually using <see cref="IJobQueue"/>,
|
||||
/// but when enabling the <see cref="JobsFeature"/>, these jobs become available as activities too.
|
||||
/// but when enabling the <see cref="JobActivitiesFeature"/>, these jobs become available as activities too.
|
||||
/// </summary>
|
||||
public class IndexBlockchainJob : Job
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Activities.Jobs.Features;
|
||||
using Elsa.Activities.Jobs.Implementations;
|
||||
using Elsa.Activities.Jobs.Services;
|
||||
using Elsa.ActivityDefinitions.EntityFrameworkCore.Extensions;
|
||||
using Elsa.ActivityDefinitions.EntityFrameworkCore.Sqlite;
|
||||
using Elsa.Api.Common;
|
||||
|
|
@ -11,18 +8,17 @@ using Elsa.Api.Common.Options;
|
|||
using Elsa.AspNetCore.Extensions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Extensions;
|
||||
using Elsa.Hangfire.Implementations;
|
||||
using Elsa.Http;
|
||||
using Elsa.Http.Extensions;
|
||||
using Elsa.JavaScript.Activities;
|
||||
using Elsa.JavaScript.Extensions;
|
||||
using Elsa.Jobs.Extensions;
|
||||
using Elsa.Jobs.Activities.Extensions;
|
||||
using Elsa.Jobs.Activities.Implementations;
|
||||
using Elsa.Jobs.Activities.Services;
|
||||
using Elsa.Labels.EntityFrameworkCore.Extensions;
|
||||
using Elsa.Labels.EntityFrameworkCore.Sqlite;
|
||||
using Elsa.Labels.Extensions;
|
||||
using Elsa.Liquid.Extensions;
|
||||
using Elsa.Quartz.Implementations;
|
||||
using Elsa.Scheduling.Extensions;
|
||||
using Elsa.WorkflowContexts.Extensions;
|
||||
using Elsa.Workflows.Api.Extensions;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
|
|
@ -35,7 +31,6 @@ using Elsa.Workflows.Persistence.EntityFrameworkCore.Extensions;
|
|||
using Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite;
|
||||
using Elsa.Workflows.Persistence.Extensions;
|
||||
using Elsa.Workflows.Runtime.Extensions;
|
||||
using Elsa.WorkflowServer.Web;
|
||||
using Elsa.WorkflowServer.Web.Implementations;
|
||||
using Elsa.WorkflowServer.Web.Jobs;
|
||||
using FastEndpoints;
|
||||
|
|
@ -80,7 +75,7 @@ services
|
|||
feature.CredentialsValidator = sp => sp.GetRequiredService<CustomCredentialsValidator>();
|
||||
feature.AccessTokenIssuer = sp => sp.GetRequiredService<CustomAccessTokenIssuer>();
|
||||
})
|
||||
.Use<JobsFeature>()
|
||||
.UseJobActivities()
|
||||
.UseWorkflowPersistence(p => p.UseEntityFrameworkCore(ef => ef.UseSqlite()))
|
||||
.UseWorkflowApiEndpoints()
|
||||
.UseJavaScript()
|
||||
|
|
@ -91,11 +86,6 @@ services
|
|||
.UseMvc()
|
||||
);
|
||||
|
||||
services
|
||||
.AddJobServices(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
|
||||
.AddSchedulingServices()
|
||||
;
|
||||
|
||||
services.AddFastEndpoints();
|
||||
services.AddAuthenticationJWTBearer(accessTokenOptions.SigningKey);
|
||||
services.AddHealthChecks();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class Module : IModule
|
|||
{
|
||||
private record HostedServiceDescriptor(int Order, Type HostedServiceType);
|
||||
|
||||
private readonly ISet<IFeature> _configurators = new HashSet<IFeature>();
|
||||
private readonly ISet<IFeature> _features = new HashSet<IFeature>();
|
||||
private readonly ICollection<HostedServiceDescriptor> _hostedServiceDescriptors = new List<HostedServiceDescriptor>();
|
||||
|
||||
public Module(IServiceCollection services)
|
||||
|
|
@ -25,10 +25,10 @@ public class Module : IModule
|
|||
|
||||
public T Configure<T>(Func<IModule, T> factory, Action<T>? configure = default) where T : class, IFeature
|
||||
{
|
||||
if (_configurators.FirstOrDefault(x => x is T) is not T configurator)
|
||||
if (_features.FirstOrDefault(x => x is T) is not T configurator)
|
||||
{
|
||||
configurator = factory(this);
|
||||
_configurators.Add(configurator);
|
||||
_features.Add(configurator);
|
||||
}
|
||||
|
||||
configure?.Invoke(configurator);
|
||||
|
|
@ -45,18 +45,18 @@ public class Module : IModule
|
|||
{
|
||||
ResolveDependencies();
|
||||
|
||||
foreach (var configurator in _configurators)
|
||||
foreach (var feature in _features)
|
||||
{
|
||||
configurator.Configure();
|
||||
configurator.ConfigureHostedServices();
|
||||
feature.Configure();
|
||||
feature.ConfigureHostedServices();
|
||||
}
|
||||
|
||||
foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order))
|
||||
Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.HostedServiceType));
|
||||
|
||||
foreach (var configurator in _configurators)
|
||||
foreach (var feature in _features)
|
||||
{
|
||||
configurator.Apply();
|
||||
feature.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ public class Module : IModule
|
|||
{
|
||||
var resolvedDependencyTypes = new HashSet<Type>();
|
||||
|
||||
foreach (var configurator in _configurators.ToList())
|
||||
foreach (var configurator in _features.ToList())
|
||||
ResolveDependencies(configurator, resolvedDependencyTypes);
|
||||
}
|
||||
|
||||
|
|
@ -83,13 +83,13 @@ public class Module : IModule
|
|||
|
||||
private IFeature AddConfigurator(Type type)
|
||||
{
|
||||
var configurator = _configurators.FirstOrDefault(x => x.GetType() == type);
|
||||
var configurator = _features.FirstOrDefault(x => x.GetType() == type);
|
||||
|
||||
if (configurator != null)
|
||||
return configurator;
|
||||
|
||||
configurator = (IFeature)Activator.CreateInstance(type, this)!;
|
||||
_configurators.Add(configurator);
|
||||
_features.Add(configurator);
|
||||
return configurator;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
using System;
|
||||
[assembly: CLSCompliant(true)]
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
using Elsa.Jobs.Implementations;
|
||||
using Elsa.Jobs.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Jobs.Extensions;
|
||||
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddJobServices(this IServiceCollection services, IJobSchedulerProvider schedulerProvider, IJobQueueProvider queueProvider)
|
||||
{
|
||||
services
|
||||
.AddSingleton<IJobSerializer, JobSerializer>()
|
||||
.AddSingleton<IJobFactory, JobFactory>()
|
||||
.AddSingleton<IJobRunner, JobRunner>();
|
||||
|
||||
schedulerProvider.ConfigureServices(services);
|
||||
queueProvider.ConfigureServices(services);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Jobs.Services;
|
||||
|
||||
public interface IJobQueueProvider
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Jobs.Services;
|
||||
|
||||
public interface IJobSchedulerProvider
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.ServiceBus.Abstractions.Services;
|
||||
|
||||
public interface IServiceBusProvider
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.ActivityDefinitions.Entities;
|
||||
using Elsa.ActivityDefinitions.Implementations;
|
||||
using Elsa.ActivityDefinitions.Services;
|
||||
using Elsa.Common.Features;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
|
|
@ -14,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
namespace Elsa.ActivityDefinitions.Features;
|
||||
|
||||
[DependsOn(typeof(MediatorFeature))]
|
||||
[DependsOn(typeof(SystemClockFeature))]
|
||||
public class ActivityDefinitionsFeature : FeatureBase
|
||||
{
|
||||
public ActivityDefinitionsFeature(IModule module) : base(module)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Text.Json;
|
|||
using Elsa.ActivityDefinitions.Entities;
|
||||
using Elsa.ActivityDefinitions.Notifications;
|
||||
using Elsa.ActivityDefinitions.Services;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Mediator.Services;
|
||||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using Elsa.AzureServiceBus.Activities;
|
|||
using Elsa.AzureServiceBus.Models;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
13
src/modules/Elsa.Common/Elsa.Common.csproj
Normal file
13
src/modules/Elsa.Common/Elsa.Common.csproj
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Features\Elsa.Features.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
19
src/modules/Elsa.Common/Features/SystemClockFeature.cs
Normal file
19
src/modules/Elsa.Common/Features/SystemClockFeature.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Common.Implementations;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Common.Features;
|
||||
|
||||
public class SystemClockFeature : FeatureBase
|
||||
{
|
||||
public SystemClockFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
Services.AddSingleton<ISystemClock, SystemClock>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Common.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.Implementations;
|
||||
namespace Elsa.Common.Implementations;
|
||||
|
||||
public class SystemClock : ISystemClock
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Elsa.Workflows.Core.Services;
|
||||
namespace Elsa.Common.Services;
|
||||
|
||||
public interface ISystemClock
|
||||
{
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Jobs\Elsa.Jobs.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
44
src/modules/Elsa.Hangfire/Features/HangfireFeature.cs
Normal file
44
src/modules/Elsa.Hangfire/Features/HangfireFeature.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Hangfire;
|
||||
using Hangfire.MemoryStorage;
|
||||
using Hangfire.SqlServer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Hangfire.Features;
|
||||
|
||||
public class HangfireFeature : FeatureBase
|
||||
{
|
||||
public HangfireFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public bool UseSqlServerStorage { get; set; }
|
||||
public SqlServerStorageOptions? SqlServerStorageOptions { get; set; }
|
||||
public string? SqlServerConnectionString { get; set; }
|
||||
public Action<BackgroundJobServerOptions>? ConfigureBackgroundServerOptions { get; set; }
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Services.AddHangfire(configuration =>
|
||||
{
|
||||
configuration.UseSimpleAssemblyNameTypeSerializer();
|
||||
configuration.UseRecommendedSerializerSettings(json => json.TypeNameHandling = TypeNameHandling.Objects);
|
||||
|
||||
if (UseSqlServerStorage)
|
||||
{
|
||||
var storageOptions = SqlServerStorageOptions ?? new SqlServerStorageOptions();
|
||||
configuration.UseSqlServerStorage(SqlServerConnectionString, storageOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
configuration.UseMemoryStorage();
|
||||
}
|
||||
});
|
||||
|
||||
if (UseSqlServerStorage)
|
||||
Services.AddHangfireServer((_, options) => ConfigureBackgroundServerOptions?.Invoke(options), new SqlServerStorage(SqlServerConnectionString));
|
||||
else
|
||||
Services.AddHangfireServer(options => { ConfigureBackgroundServerOptions?.Invoke(options); });
|
||||
}
|
||||
}
|
||||
23
src/modules/Elsa.Hangfire/Features/HangfireJobsFeature.cs
Normal file
23
src/modules/Elsa.Hangfire/Features/HangfireJobsFeature.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Extensions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Hangfire.Implementations;
|
||||
using Elsa.Jobs.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Hangfire.Features;
|
||||
|
||||
[DependsOn(typeof(JobsFeature))]
|
||||
[DependsOn(typeof(HangfireFeature))]
|
||||
public class HangfireJobsFeature : FeatureBase
|
||||
{
|
||||
public HangfireJobsFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Use<JobsFeature>(f => f.JobQueueFactory = ActivatorUtilities.GetServiceOrCreateInstance<HangfireJobQueue>);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using Elsa.Hangfire.Jobs;
|
||||
using Elsa.Jobs.Services;
|
||||
using Hangfire;
|
||||
using Hangfire.Server;
|
||||
using Hangfire.States;
|
||||
using HangfireJob = Hangfire.Common.Job;
|
||||
|
||||
|
|
@ -16,11 +15,11 @@ public class HangfireJobQueue : IJobQueue
|
|||
_backgroundJobClient = backgroundJobClient;
|
||||
}
|
||||
|
||||
public Task<string> SubmitJobAsync(IJob job, string? queueName = default, CancellationToken cancellationToken = default)
|
||||
public Task SubmitJobAsync(IJob job, string? queueName = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var hangfireJob = HangfireJob.FromExpression<RunElsaJob>(x => x.RunAsync(job, CancellationToken.None));
|
||||
var jobId = _backgroundJobClient.Create(hangfireJob, new EnqueuedState(queueName ?? "default"));
|
||||
_backgroundJobClient.Create(hangfireJob, new EnqueuedState(queueName ?? "default"));
|
||||
|
||||
return Task.FromResult(jobId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
using Elsa.Jobs.Services;
|
||||
using Hangfire;
|
||||
using Hangfire.MemoryStorage;
|
||||
using Hangfire.SqlServer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Hangfire.Implementations;
|
||||
|
||||
public class HangfireJobQueueProvider : IJobQueueProvider
|
||||
{
|
||||
public HangfireJobQueueProvider(bool registerHangfire = true)
|
||||
{
|
||||
RegisterHangfire = registerHangfire;
|
||||
}
|
||||
|
||||
public HangfireJobQueueProvider(string sqlServerConnectionString)
|
||||
{
|
||||
SqlServerConnectionString = sqlServerConnectionString;
|
||||
UseSqlServerStorage = true;
|
||||
RegisterHangfire = true;
|
||||
}
|
||||
|
||||
public bool RegisterHangfire { get; set; }
|
||||
public bool UseSqlServerStorage { get; set; }
|
||||
public string? SqlServerConnectionString { get; set; }
|
||||
public SqlServerStorageOptions? SqlServerStorageOptions { get; set; }
|
||||
public Action<BackgroundJobServerOptions>? ConfigureBackgroundServerOptions { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
if (RegisterHangfire)
|
||||
{
|
||||
services.AddHangfire(configuration =>
|
||||
{
|
||||
configuration.UseSimpleAssemblyNameTypeSerializer();
|
||||
configuration.UseRecommendedSerializerSettings(json => json.TypeNameHandling = TypeNameHandling.Objects);
|
||||
|
||||
if (UseSqlServerStorage)
|
||||
{
|
||||
var storageOptions = SqlServerStorageOptions ?? new SqlServerStorageOptions();
|
||||
configuration.UseSqlServerStorage(SqlServerConnectionString, storageOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
configuration.UseMemoryStorage();
|
||||
}
|
||||
});
|
||||
|
||||
if (UseSqlServerStorage)
|
||||
services.AddHangfireServer((_, options) => ConfigureBackgroundServerOptions?.Invoke(options), new SqlServerStorage(SqlServerConnectionString));
|
||||
else
|
||||
services.AddHangfireServer(options =>
|
||||
{
|
||||
ConfigureBackgroundServerOptions?.Invoke(options);
|
||||
});
|
||||
}
|
||||
|
||||
services.AddSingleton<IJobQueue, HangfireJobQueue>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using Elsa.Jobs.Services;
|
||||
using Hangfire.Server;
|
||||
|
||||
namespace Elsa.Hangfire.Jobs;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Elsa.Activities.Jobs.Models;
|
||||
using Elsa.Jobs.Activities.Models;
|
||||
using Elsa.Jobs.Services;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Jobs.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// Executes a given job, suspending execution of the workflow until the job finishes.
|
||||
/// Executes a job of a given type, suspending execution of the workflow until the job finishes.
|
||||
/// </summary>
|
||||
public class JobActivity : ActivityBase
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Elsa.Activities.Jobs.Attributes;
|
||||
namespace Elsa.Jobs.Activities.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class JobAttribute : Attribute
|
||||
|
|
@ -7,14 +7,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Jobs\Elsa.Jobs.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Options" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.Jobs.Activities.Features;
|
||||
|
||||
namespace Elsa.Jobs.Activities.Extensions;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule UseJobActivities(this IModule module, Action<JobActivitiesFeature>? configure = default )
|
||||
{
|
||||
module.Configure(configure);
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
using Elsa.Activities.Jobs.Implementations;
|
||||
using Elsa.Activities.Jobs.Services;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Jobs.Activities.Implementations;
|
||||
using Elsa.Jobs.Activities.Services;
|
||||
using Elsa.Jobs.Features;
|
||||
using Elsa.Mediator.Extensions;
|
||||
using Elsa.Mediator.Features;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Features;
|
||||
namespace Elsa.Jobs.Activities.Features;
|
||||
|
||||
[DependsOn(typeof(MediatorFeature))]
|
||||
public class JobsFeature : FeatureBase
|
||||
[DependsOn(typeof(JobsFeature))]
|
||||
public class JobActivitiesFeature : FeatureBase
|
||||
{
|
||||
public JobsFeature(IModule module) : base(module)
|
||||
public JobActivitiesFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
using Elsa.Activities.Jobs.Helpers;
|
||||
using Elsa.Activities.Jobs.Models;
|
||||
using Elsa.Jobs.Activities.Helpers;
|
||||
using Elsa.Jobs.Activities.Models;
|
||||
using Elsa.Jobs.Notifications;
|
||||
using Elsa.Mediator.Services;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Handlers;
|
||||
namespace Elsa.Jobs.Activities.Handlers;
|
||||
|
||||
public class JobExecutedHandler : INotificationHandler<JobExecuted>
|
||||
{
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Activities.Jobs.Attributes;
|
||||
using Elsa.Jobs.Activities.Attributes;
|
||||
using Elsa.Jobs.Services;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Helpers;
|
||||
namespace Elsa.Jobs.Activities.Helpers;
|
||||
|
||||
public static class JobTypeNameHelper
|
||||
{
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using Elsa.Activities.Jobs.Activities;
|
||||
using Elsa.Activities.Jobs.Attributes;
|
||||
using Elsa.Activities.Jobs.Helpers;
|
||||
using Elsa.Activities.Jobs.Services;
|
||||
using Elsa.Jobs.Activities.Attributes;
|
||||
using Elsa.Jobs.Activities.Helpers;
|
||||
using Elsa.Jobs.Activities.Services;
|
||||
using Elsa.Jobs.Services;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
|
@ -11,7 +11,7 @@ using Elsa.Workflows.Management.Extensions;
|
|||
using Elsa.Workflows.Management.Services;
|
||||
using Humanizer;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Implementations;
|
||||
namespace Elsa.Jobs.Activities.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Provides activity descriptors based on registered <see cref="IJob"/> implementations.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using Elsa.Activities.Jobs.Services;
|
||||
using Elsa.Jobs.Activities.Services;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Implementations;
|
||||
namespace Elsa.Jobs.Activities.Implementations;
|
||||
|
||||
public class JobRegistry : IJobRegistry
|
||||
{
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Activities.Jobs.Models;
|
||||
namespace Elsa.Jobs.Activities.Models;
|
||||
|
||||
public record EnqueuedJobPayload
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Elsa.Activities.Jobs.Services;
|
||||
namespace Elsa.Jobs.Activities.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registry of jobs.
|
||||
|
|
@ -4,6 +4,9 @@ using Elsa.Jobs.Services;
|
|||
|
||||
namespace Elsa.Jobs.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for job implementations.
|
||||
/// </summary>
|
||||
public abstract class Job : IJob
|
||||
{
|
||||
public string Id { get; set; } = default!;
|
||||
|
|
@ -8,11 +8,13 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cronos" Version="0.7.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Mediator\Elsa.Mediator.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Common\Elsa.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
26
src/modules/Elsa.Jobs/Extensions/ModuleExtensions.cs
Normal file
26
src/modules/Elsa.Jobs/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Threading.Channels;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Jobs.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Jobs.Extensions;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule UseJobs(this IModule module, Action<JobsFeature>? configure = default )
|
||||
{
|
||||
module.Configure(configure);
|
||||
return module;
|
||||
}
|
||||
|
||||
public static IServiceCollection CreateChannel<T>(this IServiceCollection services) =>
|
||||
services
|
||||
.AddSingleton(CreateChannel<T>())
|
||||
.AddSingleton(CreateChannelReader<T>)
|
||||
.AddSingleton(CreateChannelWriter<T>);
|
||||
|
||||
private static Channel<T> CreateChannel<T>() => Channel.CreateUnbounded<T>(new UnboundedChannelOptions());
|
||||
private static ChannelReader<T> CreateChannelReader<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Reader;
|
||||
private static ChannelWriter<T> CreateChannelWriter<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Writer;
|
||||
}
|
||||
44
src/modules/Elsa.Jobs/Features/JobsFeature.cs
Normal file
44
src/modules/Elsa.Jobs/Features/JobsFeature.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using Elsa.Common.Features;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Jobs.Extensions;
|
||||
using Elsa.Jobs.HostedServices;
|
||||
using Elsa.Jobs.Implementations;
|
||||
using Elsa.Jobs.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Jobs.Features;
|
||||
|
||||
[DependsOn(typeof(SystemClockFeature))]
|
||||
public class JobsFeature : FeatureBase
|
||||
{
|
||||
public JobsFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public Func<IServiceProvider, IJobQueue> JobQueueFactory { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance<LocalJobQueue>;
|
||||
public Func<IServiceProvider, IJobScheduler> JobSchedulerFactory { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance<LocalJobScheduler>;
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Services
|
||||
.AddSingleton<IJobSerializer, JobSerializer>()
|
||||
.AddSingleton<IJobFactory, JobFactory>()
|
||||
.AddSingleton<IJobRunner, JobRunner>();
|
||||
}
|
||||
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
Services.AddHostedService<JobQueueHostedService>();
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
.AddSingleton(JobQueueFactory)
|
||||
.AddSingleton(JobSchedulerFactory)
|
||||
.CreateChannel<IJob>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Jobs.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Jobs.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Continuously reads from a channel to which jobs can be sent, executing each received job.
|
||||
/// </summary>
|
||||
public class JobQueueHostedService : BackgroundService
|
||||
{
|
||||
private readonly ChannelReader<IJob> _channelReader;
|
||||
private readonly IJobRunner _jobRunner;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public JobQueueHostedService(ChannelReader<IJob> channelReader, IJobRunner jobRunner, ILogger<JobQueueHostedService> logger)
|
||||
{
|
||||
_channelReader = channelReader;
|
||||
_jobRunner = jobRunner;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var job in _channelReader.ReadAllAsync(cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _jobRunner.RunJobAsync(job, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An unhandled exception occured while running a job");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Elsa.Jobs.Services;
|
||||
|
||||
namespace Elsa.Jobs.Services;
|
||||
namespace Elsa.Jobs.Implementations;
|
||||
|
||||
public class JobFactory : IJobFactory
|
||||
{
|
||||
24
src/modules/Elsa.Jobs/Implementations/LocalJobQueue.cs
Normal file
24
src/modules/Elsa.Jobs/Implementations/LocalJobQueue.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Jobs.Services;
|
||||
|
||||
namespace Elsa.Jobs.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a local, in-memory queue of jobs that will be processed in-process.
|
||||
/// </summary>
|
||||
public class LocalJobQueue : IJobQueue
|
||||
{
|
||||
private readonly ChannelWriter<IJob> _channelWriter;
|
||||
|
||||
public LocalJobQueue(ChannelWriter<IJob> channelWriter)
|
||||
{
|
||||
_channelWriter = channelWriter;
|
||||
}
|
||||
|
||||
public async Task SubmitJobAsync(IJob job, string? queueName = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _channelWriter.WriteAsync(job, cancellationToken);
|
||||
}
|
||||
}
|
||||
72
src/modules/Elsa.Jobs/Implementations/LocalJobScheduler.cs
Normal file
72
src/modules/Elsa.Jobs/Implementations/LocalJobScheduler.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Jobs.ScheduledJobs;
|
||||
using Elsa.Jobs.Schedules;
|
||||
using Elsa.Jobs.Services;
|
||||
|
||||
namespace Elsa.Jobs.Implementations;
|
||||
|
||||
public class LocalJobScheduler : IJobScheduler
|
||||
{
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly IJobRunner _jobRunner;
|
||||
private readonly IDictionary<string, IScheduledJob> _scheduledJobs = new Dictionary<string, IScheduledJob>();
|
||||
|
||||
public LocalJobScheduler(ISystemClock systemClock, IJobRunner jobRunner)
|
||||
{
|
||||
_systemClock = systemClock;
|
||||
_jobRunner = jobRunner;
|
||||
}
|
||||
|
||||
public Task ScheduleAsync(IJob job, string name, ISchedule schedule, string[]? groupKeys = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (schedule)
|
||||
{
|
||||
case CronSchedule cronSchedule:
|
||||
{
|
||||
var scheduledJob = new ScheduledCronJob(name, job, _systemClock, cronSchedule.CronExpression, _jobRunner, cancellationToken);
|
||||
RegisterScheduledJob(scheduledJob);
|
||||
break;
|
||||
}
|
||||
case RecurringSchedule recurringSchedule:
|
||||
{
|
||||
var scheduledJob = new ScheduledRecurringJob(name, job, _systemClock, recurringSchedule.StartAt, recurringSchedule.Interval, _jobRunner, cancellationToken);
|
||||
RegisterScheduledJob(scheduledJob);
|
||||
break;
|
||||
}
|
||||
case SpecificInstantSchedule specificInstantSchedule:
|
||||
{
|
||||
var scheduledJob = new ScheduledSpecificInstantJob(name, job, _systemClock, specificInstantSchedule.DateTime, _jobRunner, cancellationToken);
|
||||
RegisterScheduledJob(scheduledJob);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException($"Schedule of type {schedule.GetType()} is not supported.");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void RegisterScheduledJob(IScheduledJob scheduledJob)
|
||||
{
|
||||
if (_scheduledJobs.TryGetValue(scheduledJob.Name, out var existingScheduledJob)) existingScheduledJob.Cancel();
|
||||
_scheduledJobs[scheduledJob.Name] = scheduledJob;
|
||||
}
|
||||
|
||||
public Task UnscheduleAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_scheduledJobs.TryGetValue(name, out var existingScheduledJob)) existingScheduledJob.Cancel();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ClearAsync(string[]? groupKeys = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var scheduledJob in _scheduledJobs.Values) scheduledJob.Cancel();
|
||||
|
||||
_scheduledJobs.Clear();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
59
src/modules/Elsa.Jobs/ScheduledJobs/ScheduledCronJob.cs
Normal file
59
src/modules/Elsa.Jobs/ScheduledJobs/ScheduledCronJob.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System.Threading;
|
||||
using Cronos;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Jobs.Services;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Elsa.Jobs.ScheduledJobs;
|
||||
|
||||
public class ScheduledCronJob : IScheduledJob
|
||||
{
|
||||
private readonly IJob _job;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly IJobRunner _jobRunner;
|
||||
private readonly CronExpression _parsedCronExpression;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private Timer? _timer;
|
||||
|
||||
public ScheduledCronJob(string name, IJob job, ISystemClock systemClock, string cronExpression, IJobRunner jobRunner, CancellationToken cancellationToken)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
_job = job;
|
||||
_systemClock = systemClock;
|
||||
_parsedCronExpression = CronExpression.Parse(cronExpression);
|
||||
_jobRunner = jobRunner;
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
Schedule();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public void Cancel() => _timer?.Dispose();
|
||||
|
||||
private void Schedule()
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
var parsedCronExpression = _parsedCronExpression;
|
||||
var nextOccurence = parsedCronExpression.GetNextOccurrence(now.UtcDateTime)!;
|
||||
|
||||
var delay = nextOccurence.Value - now;
|
||||
|
||||
if (delay.Milliseconds <= 0)
|
||||
{
|
||||
Schedule();
|
||||
return;
|
||||
}
|
||||
|
||||
_timer = new Timer(delay.TotalMilliseconds);
|
||||
|
||||
_timer.Elapsed += async (_, _) =>
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
|
||||
if (!_cancellationToken.IsCancellationRequested) await _jobRunner.RunJobAsync(_job, _cancellationToken);
|
||||
if (!_cancellationToken.IsCancellationRequested) Schedule();
|
||||
};
|
||||
}
|
||||
}
|
||||
58
src/modules/Elsa.Jobs/ScheduledJobs/ScheduledRecurringJob.cs
Normal file
58
src/modules/Elsa.Jobs/ScheduledJobs/ScheduledRecurringJob.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Jobs.Services;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Elsa.Jobs.ScheduledJobs;
|
||||
|
||||
public class ScheduledRecurringJob : IScheduledJob
|
||||
{
|
||||
private readonly IJob _job;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly TimeSpan _interval;
|
||||
private readonly IJobRunner _jobRunner;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private DateTimeOffset _startAt;
|
||||
private Timer? _timer;
|
||||
|
||||
public ScheduledRecurringJob(string name, IJob job, ISystemClock systemClock, DateTimeOffset startAt, TimeSpan interval, IJobRunner jobRunner, CancellationToken cancellationToken)
|
||||
{
|
||||
Name = name;
|
||||
_job = job;
|
||||
_systemClock = systemClock;
|
||||
_startAt = startAt;
|
||||
_interval = interval;
|
||||
_jobRunner = jobRunner;
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
Schedule();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public void Cancel() => _timer?.Dispose();
|
||||
|
||||
private void Schedule()
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
var delay = now - _startAt;
|
||||
|
||||
if (delay.Milliseconds <= 0)
|
||||
{
|
||||
Schedule();
|
||||
return;
|
||||
}
|
||||
|
||||
_timer = new Timer(delay.TotalMilliseconds);
|
||||
|
||||
_timer.Elapsed += async (_, _) =>
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
_startAt = now + _interval;
|
||||
|
||||
if (!_cancellationToken.IsCancellationRequested) await _jobRunner.RunJobAsync(_job, _cancellationToken);
|
||||
if (!_cancellationToken.IsCancellationRequested) Schedule();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Jobs.Services;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Elsa.Jobs.ScheduledJobs;
|
||||
|
||||
public class ScheduledSpecificInstantJob : IScheduledJob
|
||||
{
|
||||
private readonly IJob _job;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly DateTimeOffset _startAt;
|
||||
private readonly IJobRunner _jobRunner;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private Timer? _timer;
|
||||
|
||||
public ScheduledSpecificInstantJob(string name, IJob job, ISystemClock systemClock, DateTimeOffset startAt, IJobRunner jobRunner, CancellationToken cancellationToken)
|
||||
{
|
||||
_job = job;
|
||||
_systemClock = systemClock;
|
||||
_startAt = startAt;
|
||||
_jobRunner = jobRunner;
|
||||
_cancellationToken = cancellationToken;
|
||||
Name = name;
|
||||
|
||||
Schedule();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public void Cancel() => _timer?.Dispose();
|
||||
|
||||
private void Schedule()
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
var delay = now - _startAt;
|
||||
|
||||
if (delay.Milliseconds <= 0)
|
||||
delay = TimeSpan.Zero;
|
||||
|
||||
_timer = new Timer(delay.TotalMilliseconds);
|
||||
|
||||
_timer.Elapsed += async (_, _) =>
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
|
||||
if (!_cancellationToken.IsCancellationRequested) await _jobRunner.RunJobAsync(_job, _cancellationToken);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@ using Elsa.Jobs.Models;
|
|||
|
||||
namespace Elsa.Jobs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by types that represent a background job.
|
||||
/// </summary>
|
||||
public interface IJob
|
||||
{
|
||||
string Id { get; set; }
|
||||
|
|
@ -3,9 +3,12 @@ using System;
|
|||
namespace Elsa.Jobs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates new jobs of a given type.
|
||||
/// Instantiates jobs of a given type.
|
||||
/// </summary>
|
||||
public interface IJobFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a job of the specified type.
|
||||
/// </summary>
|
||||
IJob Create(Type jobType);
|
||||
}
|
||||
|
|
@ -8,5 +8,5 @@ namespace Elsa.Jobs.Services;
|
|||
/// </summary>
|
||||
public interface IJobQueue
|
||||
{
|
||||
Task<string> SubmitJobAsync(IJob job, string? queueName = default, CancellationToken cancellationToken = default);
|
||||
Task SubmitJobAsync(IJob job, string? queueName = default, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -3,7 +3,13 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Elsa.Jobs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Runs specified jobs.
|
||||
/// </summary>
|
||||
public interface IJobRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the specified job.
|
||||
/// </summary>
|
||||
Task RunJobAsync(IJob job, CancellationToken cancellationToken = default);
|
||||
}
|
||||
7
src/modules/Elsa.Jobs/Services/IScheduledJob.cs
Normal file
7
src/modules/Elsa.Jobs/Services/IScheduledJob.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Elsa.Jobs.Services;
|
||||
|
||||
internal interface IScheduledJob
|
||||
{
|
||||
string Name { get; set; }
|
||||
void Cancel();
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.MassTransit.Extensions;
|
||||
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddMassTransitServices(this IServiceCollection services)
|
||||
{
|
||||
return services.AddSingleton<IWorkflowDispatcher, MassTransitWorkflowDispatcher>();
|
||||
}
|
||||
}
|
||||
13
src/modules/Elsa.MassTransit/Extensions/ModuleExtensions.cs
Normal file
13
src/modules/Elsa.MassTransit/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.MassTransit.Features;
|
||||
|
||||
namespace Elsa.MassTransit.Extensions;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule AddMassTransitWorkflowDispatchers(this IModule module)
|
||||
{
|
||||
module.Configure<MassTransitDispatchersFeature>();
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.Workflows.Runtime.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.MassTransit.Features;
|
||||
|
||||
[DependsOn(typeof(WorkflowRuntimeFeature))]
|
||||
[DependsOn(typeof(MassTransitServiceBusFeature))]
|
||||
public class MassTransitDispatchersFeature : FeatureBase
|
||||
{
|
||||
public MassTransitDispatchersFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<WorkflowRuntimeFeature>(f => f.WorkflowDispatcherFactory = ActivatorUtilities.GetServiceOrCreateInstance<MassTransitWorkflowDispatcher>);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.ServiceBus.Abstractions.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.MassTransit.Features;
|
||||
|
||||
public class MassTransitServiceBusFeature : FeatureBase
|
||||
{
|
||||
public MassTransitServiceBusFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
Services.AddSingleton<IServiceBus, MassTransitServiceBus>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
using Elsa.ServiceBus.Abstractions.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.MassTransit.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// A MassTransit implementation for <see cref="IServiceBus"/>.
|
||||
/// </summary>
|
||||
public class MassTransitServiceBusProvider : IServiceBusProvider
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddSingleton<IServiceBus, MassTransitServiceBus>();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using System.Threading.Channels;
|
||||
using Elsa.Mediator.Implementations;
|
||||
using Elsa.Mediator.Middleware.Command;
|
||||
using Elsa.Mediator.Middleware.Command.Contracts;
|
||||
|
|
@ -10,6 +11,7 @@ using Elsa.Mediator.Models;
|
|||
using Elsa.Mediator.Services;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Mediator.Features;
|
||||
using Elsa.Mediator.HostedServices;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Mediator.Extensions;
|
||||
|
|
@ -29,9 +31,16 @@ public static class DependencyInjectionExtensions
|
|||
.AddSingleton<IRequestSender>(sp => sp.GetRequiredService<IMediator>())
|
||||
.AddSingleton<ICommandSender>(sp => sp.GetRequiredService<IMediator>())
|
||||
.AddSingleton<IEventPublisher>(sp => sp.GetRequiredService<IMediator>())
|
||||
.AddSingleton<IBackgroundCommandSender, BackgroundCommandSender>()
|
||||
.AddSingleton<IBackgroundEventPublisher, BackgroundEventPublisher>()
|
||||
.AddSingleton<IRequestPipeline, RequestPipeline>()
|
||||
.AddSingleton<ICommandPipeline, CommandPipeline>()
|
||||
.AddSingleton<INotificationPipeline, NotificationPipeline>();
|
||||
.AddSingleton<INotificationPipeline, NotificationPipeline>()
|
||||
.AddHostedService<BackgroundCommandSenderHostedService>()
|
||||
.AddHostedService<BackgroundEventPublisherHostedService>()
|
||||
.CreateChannel<ICommand>()
|
||||
.CreateChannel<INotification>()
|
||||
;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommandHandler<THandler, TCommand>(this IServiceCollection services)
|
||||
|
|
@ -76,6 +85,12 @@ public static class DependencyInjectionExtensions
|
|||
public static IServiceCollection AddCommandHandlersFrom(this IServiceCollection services, Type markerType) => services.AddHandlersFromInternal<ICommandHandler>(markerType);
|
||||
public static IServiceCollection AddCommandHandlersFrom(this IServiceCollection services, Assembly assembly) => services.AddHandlersFromInternal<ICommandHandler>(assembly);
|
||||
|
||||
public static IServiceCollection CreateChannel<T>(this IServiceCollection services) =>
|
||||
services
|
||||
.AddSingleton(CreateChannel<T>())
|
||||
.AddSingleton(CreateChannelReader<T>)
|
||||
.AddSingleton(CreateChannelWriter<T>);
|
||||
|
||||
|
||||
private static IServiceCollection AddHandlersFromInternal<TService, TMarker>(this IServiceCollection services) => services.AddHandlersFromInternal<TService>(typeof(TMarker));
|
||||
private static IServiceCollection AddHandlersFromInternal<TService>(this IServiceCollection services, Type assemblyMarkerType) => services.AddHandlersFromInternal<TService>(assemblyMarkerType.Assembly);
|
||||
|
|
@ -90,4 +105,8 @@ public static class DependencyInjectionExtensions
|
|||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static Channel<T> CreateChannel<T>() => Channel.CreateUnbounded<T>(new UnboundedChannelOptions());
|
||||
private static ChannelReader<T> CreateChannelReader<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Reader;
|
||||
private static ChannelWriter<T> CreateChannelWriter<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Writer;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using System.Threading.Channels;
|
||||
using Elsa.Mediator.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Mediator.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Continuously reads from a channel to which commands can be sent, executing each received command.
|
||||
/// </summary>
|
||||
public class BackgroundCommandSenderHostedService : BackgroundService
|
||||
{
|
||||
private readonly ChannelReader<ICommand> _channelReader;
|
||||
private readonly ICommandSender _commandSender;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public BackgroundCommandSenderHostedService(ChannelReader<ICommand> channelReader, ICommandSender commandSender, ILogger<BackgroundCommandSenderHostedService> logger)
|
||||
{
|
||||
_channelReader = channelReader;
|
||||
_commandSender = commandSender;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var request in _channelReader.ReadAllAsync(cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _commandSender.ExecuteAsync(request, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An unhandled exception occured while processing the queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using System.Threading.Channels;
|
||||
using Elsa.Mediator.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Mediator.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Continuously reads from a channel to which notifications can be sent, publishing each received notification.
|
||||
/// </summary>
|
||||
public class BackgroundEventPublisherHostedService : BackgroundService
|
||||
{
|
||||
private readonly ChannelReader<INotification> _channelReader;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public BackgroundEventPublisherHostedService(ChannelReader<INotification> channelReader, IEventPublisher eventPublisher, ILogger<BackgroundEventPublisherHostedService> logger)
|
||||
{
|
||||
_channelReader = channelReader;
|
||||
_eventPublisher = eventPublisher;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var notification in _channelReader.ReadAllAsync(cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _eventPublisher.PublishAsync(notification, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "An unhandled exception occured while processing the queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Channels;
|
||||
using Elsa.Mediator.Services;
|
||||
|
||||
namespace Elsa.Mediator.Implementations;
|
||||
|
||||
public class BackgroundCommandSender : IBackgroundCommandSender
|
||||
{
|
||||
private readonly ChannelWriter<ICommand> _channelWriter;
|
||||
public BackgroundCommandSender(ChannelWriter<ICommand> channelWriter) => _channelWriter = channelWriter;
|
||||
public async Task SendAsync(ICommand command, CancellationToken cancellationToken = default) => await _channelWriter.WriteAsync(command, cancellationToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Channels;
|
||||
using Elsa.Mediator.Services;
|
||||
|
||||
namespace Elsa.Mediator.Implementations;
|
||||
|
||||
public class BackgroundEventPublisher : IBackgroundEventPublisher
|
||||
{
|
||||
private readonly ChannelWriter<INotification> _channelWriter;
|
||||
public BackgroundEventPublisher(ChannelWriter<INotification> channelWriter) => _channelWriter = channelWriter;
|
||||
public async Task PublishAsync(INotification notification, CancellationToken cancellationToken = default) => await _channelWriter.WriteAsync(notification, cancellationToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.Mediator.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Send requests to be processed asynchronously in the background.
|
||||
/// </summary>
|
||||
public interface IBackgroundCommandSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Submits the specified command to a channel writer. The channel is processed asynchronously from a background service.
|
||||
/// </summary>
|
||||
Task SendAsync(ICommand command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.Mediator.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Publish notifications to be processed asynchronously in the background.
|
||||
/// </summary>
|
||||
public interface IBackgroundEventPublisher
|
||||
{
|
||||
/// <summary>
|
||||
/// Publish the specified notification using from a background service.
|
||||
/// </summary>
|
||||
Task PublishAsync(INotification notification, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ using Elsa.Workflows.Core.Serialization;
|
|||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Persistence.Entities;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Proto;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Runtime.Protos;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Proto.Cluster;
|
||||
|
||||
namespace Elsa.ProtoActor.Implementations;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using Elsa.Workflows.Core.Models;
|
|||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Persistence.Entities;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Proto;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Jobs\Elsa.Jobs.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Scheduling\Elsa.Scheduling.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
48
src/modules/Elsa.Quartz/Features/QuartzFeature.cs
Normal file
48
src/modules/Elsa.Quartz/Features/QuartzFeature.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Quartz.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Features;
|
||||
|
||||
public class QuartzFeature : FeatureBase
|
||||
{
|
||||
public QuartzFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public Action<QuartzOptions>? ConfigureQuartzOptions { get; set; }
|
||||
public Action<IServiceCollectionQuartzConfigurator>? ConfigureQuartz { get; set; }
|
||||
public Action<QuartzHostedServiceOptions>? ConfigureQuartzHostedService { get; set; }
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
if (ConfigureQuartzOptions != null)
|
||||
Services.Configure(ConfigureQuartzOptions);
|
||||
|
||||
Services
|
||||
.AddQuartz(configure =>
|
||||
{
|
||||
ConfigureQuartzInternal(configure, ConfigureQuartz);
|
||||
configure.AddElsaJobs();
|
||||
});
|
||||
}
|
||||
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
Services.AddQuartzHostedService(options =>
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
ConfigureQuartzHostedService?.Invoke(options);
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzInternal(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
quartz.UseMicrosoftDependencyInjectionJobFactory();
|
||||
quartz.UseSimpleTypeLoader();
|
||||
quartz.UseInMemoryStore();
|
||||
configureQuartz?.Invoke(quartz);
|
||||
}
|
||||
}
|
||||
22
src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs
Normal file
22
src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Jobs.Features;
|
||||
using Elsa.Quartz.Implementations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Quartz.Features;
|
||||
|
||||
[DependsOn(typeof(JobsFeature))]
|
||||
[DependsOn(typeof(QuartzFeature))]
|
||||
public class QuartzSchedulerFeature : FeatureBase
|
||||
{
|
||||
public QuartzSchedulerFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<JobsFeature>(f => f.JobSchedulerFactory = ActivatorUtilities.GetServiceOrCreateInstance<QuartzJobScheduler>);
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ public class QuartzJobScheduler : IJobScheduler
|
|||
}
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Schedule of type {schedule.GetType()} is not supported. But if you create an issue, we'll make this logic extensible & replaceable :)");
|
||||
throw new NotSupportedException($"Schedule of type {schedule.GetType()} is not supported.");
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
using Elsa.Jobs.Services;
|
||||
using Elsa.Quartz.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// A Quartz.NET implementation for <see cref="IJobSchedulerProvider"/>.
|
||||
/// </summary>
|
||||
public class QuartzJobSchedulerProvider : IJobSchedulerProvider
|
||||
{
|
||||
public QuartzJobSchedulerProvider(bool registerQuartz = true)
|
||||
{
|
||||
RegisterQuartz = registerQuartz;
|
||||
}
|
||||
|
||||
public QuartzJobSchedulerProvider(Action<QuartzOptions> configureQuartzOptions)
|
||||
{
|
||||
RegisterQuartz = true;
|
||||
ConfigureQuartzOptions = configureQuartzOptions;
|
||||
}
|
||||
|
||||
public QuartzJobSchedulerProvider(
|
||||
Action<QuartzOptions> configureQuartzOptions,
|
||||
Action<IServiceCollectionQuartzConfigurator> configureQuartz)
|
||||
{
|
||||
RegisterQuartz = true;
|
||||
ConfigureQuartzOptions = configureQuartzOptions;
|
||||
ConfigureQuartz = configureQuartz;
|
||||
}
|
||||
|
||||
public QuartzJobSchedulerProvider(
|
||||
Action<QuartzOptions> configureQuartzOptions,
|
||||
Action<IServiceCollectionQuartzConfigurator> configureQuartz,
|
||||
Action<QuartzHostedServiceOptions> configureQuartzHostedService)
|
||||
{
|
||||
RegisterQuartz = true;
|
||||
ConfigureQuartzOptions = configureQuartzOptions;
|
||||
ConfigureQuartz = configureQuartz;
|
||||
ConfigureQuartzHostedService = configureQuartzHostedService;
|
||||
}
|
||||
|
||||
public bool RegisterQuartz { get; set; }
|
||||
public Action<QuartzOptions>? ConfigureQuartzOptions { get; set; }
|
||||
public Action<IServiceCollectionQuartzConfigurator>? ConfigureQuartz { get; set; }
|
||||
public Action<QuartzHostedServiceOptions>? ConfigureQuartzHostedService { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
if (RegisterQuartz)
|
||||
{
|
||||
if (ConfigureQuartzOptions != null)
|
||||
services.Configure(ConfigureQuartzOptions);
|
||||
|
||||
services
|
||||
.AddQuartz(configure =>
|
||||
{
|
||||
ConfigureQuartzInternal(configure, ConfigureQuartz);
|
||||
configure.AddElsaJobs();
|
||||
})
|
||||
.AddQuartzHostedService(options =>
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
ConfigureQuartzHostedService?.Invoke(options);
|
||||
});
|
||||
}
|
||||
|
||||
services.AddSingleton<IJobScheduler, QuartzJobScheduler>();
|
||||
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzInternal(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
quartz.UseMicrosoftDependencyInjectionJobFactory();
|
||||
quartz.UseSimpleTypeLoader();
|
||||
quartz.UseInMemoryStore();
|
||||
configureQuartz?.Invoke(quartz);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Jobs.Abstractions\Elsa.Jobs.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Jobs\Elsa.Jobs.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
using Elsa.Mediator.Extensions;
|
||||
using Elsa.Scheduling.Implementations;
|
||||
using Elsa.Scheduling.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Scheduling.Extensions;
|
||||
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSchedulingServices(this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddSingleton<IWorkflowTriggerScheduler, WorkflowTriggerScheduler>()
|
||||
.AddSingleton<IWorkflowBookmarkScheduler, WorkflowBookmarkScheduler>()
|
||||
.AddNotificationHandlersFrom<HostedServices.ScheduleWorkflows>()
|
||||
.AddHostedService<HostedServices.ScheduleWorkflows>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
14
src/modules/Elsa.Scheduling/Extensions/ModuleExtensions.cs
Normal file
14
src/modules/Elsa.Scheduling/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Scheduling.Features;
|
||||
|
||||
namespace Elsa.Scheduling.Extensions;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule UseScheduling(this IModule module, Action<SchedulingFeature>? configure = default )
|
||||
{
|
||||
module.Configure(configure);
|
||||
return module;
|
||||
}
|
||||
}
|
||||
32
src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs
Normal file
32
src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Elsa.Common.Features;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Mediator.Extensions;
|
||||
using Elsa.Scheduling.HostedServices;
|
||||
using Elsa.Scheduling.Implementations;
|
||||
using Elsa.Scheduling.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Scheduling.Features;
|
||||
|
||||
[DependsOn(typeof(SystemClockFeature))]
|
||||
public class SchedulingFeature : FeatureBase
|
||||
{
|
||||
public SchedulingFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
Services.AddHostedService<ScheduleWorkflowsHostedService>();
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
.AddSingleton<IWorkflowTriggerScheduler, WorkflowTriggerScheduler>()
|
||||
.AddSingleton<IWorkflowBookmarkScheduler, WorkflowBookmarkScheduler>()
|
||||
.AddNotificationHandlersFrom<ScheduleWorkflowsHostedService>();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,12 @@ namespace Elsa.Scheduling.HostedServices;
|
|||
/// <summary>
|
||||
/// Loads all timer-specific workflow bookmarks from the database and create scheduled jobs for them.
|
||||
/// </summary>
|
||||
public class ScheduleWorkflows : BackgroundService
|
||||
public class ScheduleWorkflowsHostedService : BackgroundService
|
||||
{
|
||||
private readonly IWorkflowBookmarkStore _bookmarkStore;
|
||||
private readonly IWorkflowBookmarkScheduler _workflowBookmarkScheduler;
|
||||
|
||||
public ScheduleWorkflows(IWorkflowBookmarkStore bookmarkStore, IWorkflowBookmarkScheduler workflowBookmarkScheduler)
|
||||
public ScheduleWorkflowsHostedService(IWorkflowBookmarkStore bookmarkStore, IWorkflowBookmarkScheduler workflowBookmarkScheduler)
|
||||
{
|
||||
_bookmarkStore = bookmarkStore;
|
||||
_workflowBookmarkScheduler = workflowBookmarkScheduler;
|
||||
|
|
@ -4,7 +4,6 @@ using System.Threading.Tasks;
|
|||
using Elsa.Jobs.Abstractions;
|
||||
using Elsa.Jobs.Models;
|
||||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||
using Elsa.AspNetCore.Attributes;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Elsa.AspNetCore.Attributes;
|
|||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Elsa.AspNetCore.Attributes;
|
|||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Threading.Tasks;
|
|||
using Elsa.AspNetCore.Attributes;
|
||||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Api.ApiResults;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using Elsa.AspNetCore.Attributes;
|
|||
using Elsa.Persistence.Common.Models;
|
||||
using Elsa.Workflows.Api.ApiResults;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Elsa.Persistence.Common.Models;
|
|||
using Elsa.Workflows.Api.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Persistence.Models;
|
||||
using Elsa.Workflows.Persistence.Services;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Humanizer;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue