Refactor Quartz module and add Hangfire provider for JobQueue service

This commit is contained in:
Sipke Schoorstra 2022-03-08 12:59:35 +01:00
parent f5aeaf9120
commit fc5f14a18e
17 changed files with 162 additions and 104 deletions

View file

@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Jobs.Contracts;
public interface ISchedulingServiceProvider
public interface IJobQueueProvider
{
void ConfigureServices(IServiceCollection services);
}

View file

@ -0,0 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Jobs.Contracts;
public interface IJobSchedulerProvider
{
void ConfigureServices(IServiceCollection services);
}

View file

@ -0,0 +1,7 @@
namespace Elsa.Jobs.Contracts;
public interface IJobSerializer
{
string Serialize(IJob job);
T Deserialize<T>(string json) where T : IJob;
}

View file

@ -6,11 +6,14 @@ namespace Elsa.Jobs.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddJobs(this IServiceCollection services, ISchedulingServiceProvider serviceProvider)
public static IServiceCollection AddJobs(this IServiceCollection services, IJobSchedulerProvider schedulerProvider, IJobQueueProvider queueProvider)
{
services.AddSingleton<IJobRunner, JobRunner>();
services
.AddSingleton<IJobSerializer, JobSerializer>()
.AddSingleton<IJobRunner, JobRunner>();
serviceProvider.ConfigureServices(services);
schedulerProvider.ConfigureServices(services);
queueProvider.ConfigureServices(services);
return services;
}

View file

@ -1,10 +1,9 @@
using System.Text.Json;
using Elsa.Jobs.Contracts;
using Elsa.Modules.Quartz.Contracts;
namespace Elsa.Modules.Quartz.Services;
namespace Elsa.Jobs.Services;
public class ElsaJobSerializer : IElsaJobSerializer
public class JobSerializer : IJobSerializer
{
public string Serialize(IJob job)
{

View file

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hangfire.Core" Version="1.7.28" />
<PackageReference Include="Hangfire" Version="1.7.28" />
</ItemGroup>
<ItemGroup>

View file

@ -4,12 +4,4 @@ namespace Elsa.Modules.Hangfire.Extensions;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers both Hangfire and Elsa specific services.
/// If you register Hangfire yourself, use <see cref="ConfigureHangfireModule"/>
/// </summary>
public static IServiceCollection AddHangfireAndModule(this IServiceCollection services)
{
}
}

View file

@ -0,0 +1,41 @@
using Elsa.Jobs.Contracts;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Modules.Hangfire.Services;
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 void ConfigureServices(IServiceCollection services)
{
if (RegisterHangfire)
{
var storageOptions = SqlServerStorageOptions ?? new SqlServerStorageOptions();
services
.AddHangfire(configuration => configuration
.UseSimpleAssemblyNameTypeSerializer()
.UseSqlServerStorage(SqlServerConnectionString, storageOptions));
}
services.AddSingleton<IJobQueue, HangfireJobQueue>();
}
}

View file

@ -1,9 +0,0 @@
using Elsa.Jobs.Contracts;
namespace Elsa.Modules.Quartz.Contracts;
public interface IElsaJobSerializer
{
string Serialize(IJob job);
T Deserialize<T>(string json) where T : IJob;
}

View file

@ -16,4 +16,8 @@
<ProjectReference Include="..\Elsa.Modules.Scheduling\Elsa.Modules.Scheduling.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Contracts" />
</ItemGroup>
</Project>

View file

@ -1,9 +1,5 @@
using Elsa.Jobs.Contracts;
using Elsa.Modules.Quartz.Contracts;
using Elsa.Modules.Quartz.Jobs;
using Elsa.Modules.Quartz.Services;
using Elsa.Modules.Scheduling.Jobs;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using IJob = Elsa.Jobs.Contracts.IJob;
@ -11,43 +7,7 @@ namespace Elsa.Modules.Quartz.Extensions;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers both Quartz as well as Elsa-specific services and jobs.
/// If you register Quartz yourself, use <see cref="AddQuartzModule"/> instead.
/// </summary>
public static IServiceCollection AddQuartzAndModule(
this IServiceCollection services,
Action<QuartzOptions>? configureQuartzOptions = default,
Action<IServiceCollectionQuartzConfigurator>? configureQuartz = default,
Action<QuartzHostedServiceOptions>? configureQuartzHostedService = default)
{
if (configureQuartzOptions != null)
services.Configure(configureQuartzOptions);
return services
.AddQuartz(configure =>
{
ConfigureQuartz(configure, configureQuartz);
AddQuartzModule(services, configure);
})
.AddQuartzHostedService(options => ConfigureQuartzHostedService(options, configureQuartzHostedService));
}
/// <summary>
/// This will register Elsa-specific services and jobs, but will **not** register Quartz itself. To register Quartz, you need to do so yourself, or use <see cref="AddQuartzAndModule"/> to register & configure Quartz for Elsa.
/// </summary>
public static IServiceCollection AddQuartzModule(this IServiceCollection services, IServiceCollectionQuartzConfigurator quartz)
{
services
.AddSingleton<IElsaJobSerializer, ElsaJobSerializer>()
.AddSingleton<IJobScheduler, QuartzJobScheduler>();
quartz.AddElsaJobs();
return services;
}
private static IServiceCollectionQuartzConfigurator AddElsaJobs(this IServiceCollectionQuartzConfigurator quartz)
public static IServiceCollectionQuartzConfigurator AddElsaJobs(this IServiceCollectionQuartzConfigurator quartz)
{
quartz.AddJob<RunWorkflowJob>();
quartz.AddJob<ResumeWorkflowJob>();
@ -55,20 +15,6 @@ public static class ServiceCollectionExtensions
return quartz;
}
private static void ConfigureQuartzHostedService(QuartzHostedServiceOptions options, Action<QuartzHostedServiceOptions>? configureQuartzHostedService)
{
options.WaitForJobsToComplete = true;
configureQuartzHostedService?.Invoke(options);
}
private static IServiceCollectionQuartzConfigurator AddJob<TJob>(this IServiceCollectionQuartzConfigurator quartz) where TJob : IJob =>
quartz.AddJob<QuartzJob<TJob>>(job => job.StoreDurably().WithIdentity(typeof(TJob).Name));
private static void ConfigureQuartz(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
{
quartz.UseMicrosoftDependencyInjectionJobFactory();
quartz.UseSimpleTypeLoader();
quartz.UseInMemoryStore();
configureQuartz?.Invoke(quartz);
}
}

View file

@ -1,5 +1,4 @@
using Elsa.Jobs.Contracts;
using Elsa.Modules.Quartz.Contracts;
using Elsa.Modules.Quartz.Services;
using Quartz;
using IJob = Elsa.Jobs.Contracts.IJob;
@ -13,19 +12,19 @@ namespace Elsa.Modules.Quartz.Jobs;
/// <typeparam name="TElsaJob"></typeparam>
public class QuartzJob<TElsaJob> : IQuartzJob where TElsaJob : IJob
{
private readonly IElsaJobSerializer _elsaJobSerializer;
private readonly IJobSerializer _jobSerializer;
private readonly IJobRunner _jobRunner;
public QuartzJob(IElsaJobSerializer elsaJobSerializer, IJobRunner jobRunner)
public QuartzJob(IJobSerializer jobSerializer, IJobRunner jobRunner)
{
_elsaJobSerializer = elsaJobSerializer;
_jobSerializer = jobSerializer;
_jobRunner = jobRunner;
}
public async Task Execute(IJobExecutionContext context)
{
var json = context.MergedJobDataMap.GetString(QuartzJobScheduler.JobDataKey)!;
var elsaJob = _elsaJobSerializer.Deserialize<TElsaJob>(json);
var elsaJob = _jobSerializer.Deserialize<TElsaJob>(json);
await _jobRunner.RunJobAsync(elsaJob, context.CancellationToken);
}
}

View file

@ -1,6 +1,5 @@
using Elsa.Jobs.Contracts;
using Elsa.Jobs.Schedules;
using Elsa.Modules.Quartz.Contracts;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Impl.Matchers;
@ -14,13 +13,13 @@ public class QuartzJobScheduler : IJobScheduler
{
public const string JobDataKey = "ElsaJob";
public const string RootGroupKey = "ElsaJobs";
private readonly IElsaJobSerializer _elsaJobSerializer;
private readonly IJobSerializer _jobSerializer;
private readonly ISchedulerFactory _schedulerFactory;
private readonly ILogger _logger;
public QuartzJobScheduler(IElsaJobSerializer elsaJobSerializer, ISchedulerFactory schedulerFactory, ILogger<QuartzJobScheduler> logger)
public QuartzJobScheduler(IJobSerializer jobSerializer, ISchedulerFactory schedulerFactory, ILogger<QuartzJobScheduler> logger)
{
_elsaJobSerializer = elsaJobSerializer;
_jobSerializer = jobSerializer;
_schedulerFactory = schedulerFactory;
_logger = logger;
}
@ -66,7 +65,7 @@ public class QuartzJobScheduler : IJobScheduler
var jobName = job.GetType().Name;
var groupKey = BuildGroupKey(groupKeys);
var triggerKey = new TriggerKey(job.JobId, groupKey);
var json = _elsaJobSerializer.Serialize(job);
var json = _jobSerializer.Serialize(job);
var builder = TriggerBuilder.Create().ForJob(jobName).WithIdentity(triggerKey).UsingJobData(JobDataKey, json);
switch (schedule)

View file

@ -0,0 +1,80 @@
using Elsa.Jobs.Contracts;
using Elsa.Modules.Quartz.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
namespace Elsa.Modules.Quartz.Services;
/// <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);
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Jobs.Contracts;
using Elsa.Modules.Quartz.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Modules.Quartz.Services;
public class QuartzSchedulingServiceProvider : ISchedulingServiceProvider
{
public void ConfigureServices(IServiceCollection services)
{
services.AddQuartzAndModule();
}
}

View file

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\modules\Elsa.Modules.AzureServiceBus\Elsa.Modules.AzureServiceBus.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Modules.Hangfire\Elsa.Modules.Hangfire.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Modules.Http\Elsa.Modules.Http.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Modules.JavaScript\Elsa.Modules.JavaScript.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Modules.Scheduling\Elsa.Modules.Scheduling.csproj" />

View file

@ -9,6 +9,7 @@ using Elsa.Management.Extensions;
using Elsa.Mediator.Extensions;
using Elsa.Modules.AzureServiceBus.Activities;
using Elsa.Modules.AzureServiceBus.Extensions;
using Elsa.Modules.Hangfire.Services;
using Elsa.Modules.Http;
using Elsa.Modules.Http.Extensions;
using Elsa.Modules.JavaScript.Activities;
@ -43,7 +44,7 @@ services
.AddProtoActorWorkflowHost()
.IndexWorkflowTriggers()
.AddElsaManagement()
.AddJobs(new QuartzSchedulingServiceProvider())
.AddJobs(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
.AddHttpActivityServices()
.AddJobServices()
.AddAzureServiceBusServices(options => configuration.GetSection("AzureServiceBus").Bind(options))