Refactor Jobs API

This commit is contained in:
Sipke Schoorstra 2022-03-15 13:00:48 +01:00
parent 87f836b0e3
commit 059356029d
12 changed files with 94 additions and 86 deletions

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Elsa.Jobs.Models;
namespace Elsa.Jobs.Contracts;
public abstract class Job : IJob
{
ValueTask IJob.ExecuteAsync(JobExecutionContext context) => ExecuteAsync(context);
protected virtual ValueTask ExecuteAsync(JobExecutionContext context)
{
Execute(context);
return ValueTask.CompletedTask;
}
protected virtual void Execute(JobExecutionContext context)
{
}
}

View file

@ -1,22 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Jobs.Contracts;
namespace Elsa.Jobs.Abstractions;
public abstract class JobHandler<T> : IJobHandler where T : IJob
{
public bool GetSupports(IJob job) => job is T;
public virtual Task HandleAsync(IJob job, CancellationToken cancellationToken) => HandleAsync((T)job, cancellationToken);
protected virtual Task HandleAsync(T job, CancellationToken cancellationToken)
{
Handle();
return Task.CompletedTask;
}
protected virtual void Handle()
{
}
}

View file

@ -1,6 +1,9 @@
using System.Threading.Tasks;
using Elsa.Jobs.Models;
namespace Elsa.Jobs.Contracts;
public interface IJob
{
string JobId { get; }
ValueTask ExecuteAsync(JobExecutionContext context);
}

View file

@ -1,10 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace Elsa.Jobs.Contracts;
public interface IJobHandler
{
bool GetSupports(IJob job);
Task HandleAsync(IJob job, CancellationToken cancellationToken);
}

View file

@ -5,7 +5,7 @@ namespace Elsa.Jobs.Contracts;
public interface IJobScheduler
{
Task ScheduleAsync(IJob job, ISchedule schedule, string[]? groupKeys = default, CancellationToken cancellationToken = default);
Task UnscheduleAsync(IJob job, CancellationToken cancellationToken = default);
Task ScheduleAsync(IJob job, string name, ISchedule schedule, string[]? groupKeys = default, CancellationToken cancellationToken = default);
Task UnscheduleAsync(string name, CancellationToken cancellationToken = default);
Task ClearAsync(string[]? groupKeys = default, CancellationToken cancellationToken = default);
}

View file

@ -6,7 +6,7 @@ namespace Elsa.Jobs.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddJobs(this IServiceCollection services, IJobSchedulerProvider schedulerProvider, IJobQueueProvider queueProvider)
public static IServiceCollection AddJobServices(this IServiceCollection services, IJobSchedulerProvider schedulerProvider, IJobQueueProvider queueProvider)
{
services
.AddSingleton<IJobSerializer, JobSerializer>()
@ -16,6 +16,4 @@ public static class ServiceCollectionExtensions
queueProvider.ConfigureServices(services);
return services;
}
public static IServiceCollection AddJobHandler<T>(this IServiceCollection services) where T : class, IJobHandler => services.AddSingleton<IJobHandler, T>();
}

View file

@ -0,0 +1,19 @@
using System;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Jobs.Models;
public class JobExecutionContext
{
public JobExecutionContext(IServiceProvider serviceProvider, CancellationToken cancellation)
{
ServiceProvider = serviceProvider;
Cancellation = cancellation;
}
public IServiceProvider ServiceProvider { get; }
public CancellationToken Cancellation { get; }
public T GetRequiredService<T>() where T : notnull => ServiceProvider.GetRequiredService<T>();
}

View file

@ -1,28 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Jobs.Contracts;
using Elsa.Jobs.Models;
namespace Elsa.Jobs.Services;
public class JobRunner : IJobRunner
{
private readonly IEnumerable<IJobHandler> _handlers;
private readonly IServiceProvider _serviceProvider;
public JobRunner(IEnumerable<IJobHandler> handlers)
public JobRunner(IServiceProvider serviceProvider)
{
_handlers = handlers;
_serviceProvider = serviceProvider;
}
public async Task RunJobAsync(IJob job, CancellationToken cancellationToken = default)
{
var handler = _handlers.FirstOrDefault(x => x.GetSupports(job));
if (handler == null)
throw new NotSupportedException($"The specified job of type {job.GetType().Name} does not have a handler");
await handler.HandleAsync(job, cancellationToken);
var context = new JobExecutionContext(_serviceProvider, cancellationToken);
await job.ExecuteAsync(context);
}
}

View file

@ -24,16 +24,16 @@ public class QuartzJobScheduler : IJobScheduler
_logger = logger;
}
public async Task ScheduleAsync(IJob job, IElsaSchedule schedule, string[]? groupKeys, CancellationToken cancellationToken = default)
public async Task ScheduleAsync(IJob job, string name, IElsaSchedule schedule, string[]? groupKeys, CancellationToken cancellationToken = default)
{
var quartzTrigger = CreateTrigger(job, schedule, groupKeys);
var quartzTrigger = CreateTrigger(job, name, schedule, groupKeys);
await ScheduleJob(quartzTrigger, cancellationToken);
}
public async Task UnscheduleAsync(IJob job, CancellationToken cancellationToken = default)
public async Task UnscheduleAsync(string name, CancellationToken cancellationToken = default)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var triggerKey = new TriggerKey(job.JobId);
var triggerKey = new TriggerKey(name);
await scheduler.UnscheduleJob(triggerKey, cancellationToken);
}
@ -60,11 +60,11 @@ public class QuartzJobScheduler : IJobScheduler
}
}
private ITrigger CreateTrigger(IJob job, IElsaSchedule schedule, string[]? groupKeys)
private ITrigger CreateTrigger(IJob job, string name, IElsaSchedule schedule, string[]? groupKeys)
{
var jobName = job.GetType().Name;
var groupKey = BuildGroupKey(groupKeys);
var triggerKey = new TriggerKey(job.JobId, groupKey);
var triggerKey = new TriggerKey(name, groupKey);
var json = _jobSerializer.Serialize(job);
var builder = TriggerBuilder.Create().ForJob(jobName).WithIdentity(triggerKey).UsingJobData(JobDataKey, json);

View file

@ -15,8 +15,6 @@ public static class ServiceCollectionExtensions
services
.AddSingleton<IWorkflowTriggerScheduler, WorkflowTriggerScheduler>()
.AddSingleton<IWorkflowBookmarkScheduler, WorkflowBookmarkScheduler>()
.AddJobHandler<RunWorkflowJobHandler>()
.AddJobHandler<ResumeWorkflowJobHandler>()
.AddNotificationHandlersFrom<ScheduleWorkflows>()
.AddHostedService<ScheduleWorkflows>();

View file

@ -1,26 +1,33 @@
using System.Threading;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Runtime.Contracts;
using Elsa.Runtime.Models;
using Elsa.Jobs.Abstractions;
using Elsa.Jobs.Contracts;
using Elsa.Jobs.Models;
namespace Elsa.Modules.Scheduling.Jobs;
public record ResumeWorkflowJob(string WorkflowInstanceId, Bookmark Bookmark) : IJob
public class ResumeWorkflowJob : Job
{
public string JobId => $"Bookmark:{Bookmark.Id}";
}
public class ResumeWorkflowJobHandler : JobHandler<ResumeWorkflowJob>
{
private readonly IWorkflowInvoker _workflowInvoker;
public ResumeWorkflowJobHandler(IWorkflowInvoker workflowInvoker) => _workflowInvoker = workflowInvoker;
protected override async Task HandleAsync(ResumeWorkflowJob job, CancellationToken cancellationToken)
[JsonConstructor]
public ResumeWorkflowJob()
{
var request = new DispatchWorkflowInstanceRequest(job.WorkflowInstanceId, job.Bookmark);
await _workflowInvoker.DispatchAsync(request, cancellationToken);
}
public ResumeWorkflowJob(string workflowInstanceId, Bookmark bookmark)
{
WorkflowInstanceId = workflowInstanceId;
Bookmark = bookmark;
}
public string WorkflowInstanceId { get; set; } = default!;
public Bookmark Bookmark { get; set; } = default!;
protected override async ValueTask ExecuteAsync(JobExecutionContext context)
{
var request = new DispatchWorkflowInstanceRequest(WorkflowInstanceId, Bookmark);
var workflowInvoker = context.GetRequiredService<IWorkflowInvoker>();
await workflowInvoker.DispatchAsync(request, context.Cancellation);
}
}

View file

@ -1,29 +1,30 @@
using System.Threading;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Elsa.Runtime.Contracts;
using Elsa.Runtime.Models;
using Elsa.Jobs.Abstractions;
using Elsa.Jobs.Contracts;
using Elsa.Jobs.Models;
namespace Elsa.Modules.Scheduling.Jobs;
public record RunWorkflowJob(string WorkflowId) : IJob
public class RunWorkflowJob : Job
{
public string JobId => WorkflowId;
}
public class RunWorkflowJobHandler : JobHandler<RunWorkflowJob>
{
private readonly IWorkflowInvoker _workflowInvoker;
public RunWorkflowJobHandler(IWorkflowInvoker workflowInvoker)
[JsonConstructor]
public RunWorkflowJob()
{
_workflowInvoker = workflowInvoker;
}
protected override async Task HandleAsync(RunWorkflowJob job, CancellationToken cancellationToken)
public RunWorkflowJob(string workflowId)
{
var request = new DispatchWorkflowDefinitionRequest(job.WorkflowId, 1);
await _workflowInvoker.DispatchAsync(request, cancellationToken);
WorkflowId = workflowId;
}
public string WorkflowId { get; set; } = default!;
protected override async ValueTask ExecuteAsync(JobExecutionContext context)
{
var request = new DispatchWorkflowDefinitionRequest(WorkflowId, 1);
var workflowInvoker = context.GetRequiredService<IWorkflowInvoker>();
await workflowInvoker.DispatchAsync(request, context.Cancellation);
}
}