Skip to content

Commit

Permalink
Merge pull request #37 from Angeling3/hangfire-jobs
Browse files Browse the repository at this point in the history
Add hangfire implementations to the job scheduler and enqueuer
  • Loading branch information
montyclt authored Dec 10, 2024
2 parents 57eff67 + 4dc7788 commit 0df3079
Show file tree
Hide file tree
Showing 10 changed files with 460 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../MSBuild/Base.props"/>
<Import Project="../MSBuild/Packable.props"/>

<ItemGroup>
<ProjectReference Include="..\Bootstrapping\Bootstrapping.csproj" />
<ProjectReference Include="..\Foundation\Foundation.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Hangfire.Core" Version="1.8.*" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions src/ContractImplementations.Hangfire/Jobs/HangfireJobEnqueuer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using IOKode.OpinionatedFramework.Jobs;

namespace IOKode.OpinionatedFramework.ContractImplementations.Hangfire.Jobs;

public class HangfireJobEnqueuer : IJobEnqueuer
{
public Task EnqueueAsync(Queue queue, IJob job, CancellationToken cancellationToken)
{
BackgroundJob.Enqueue(queue.Name, () => InvokeJob(job));
return Task.CompletedTask;
}

public Task EnqueueWithDelayAsync(Queue queue, IJob job, TimeSpan delay, CancellationToken cancellationToken)
{
BackgroundJob.Schedule(queue.Name, () => InvokeJob(job), delay);
return Task.CompletedTask;
}

/// <summary>
/// This method is not intended to be called directly in application code.
/// Exists to allow Hangfire to serialize and deserialize the job object
/// that it will receive as an argument. This ensures that the job
/// can be processed correctly during execution.
/// </summary>
/// <remarks>
/// This method must be public because it is used by Hangfire during the deserialization
/// and execution of enqueued tasks. Hangfire requires that methods to be invoked
/// are publicly accessible to resolve them when deserializing the previously generated expression.
/// </remarks>
public async Task InvokeJob(IJob job)
{
await job.InvokeAsync(default);
}
}
51 changes: 51 additions & 0 deletions src/ContractImplementations.Hangfire/Jobs/HangfireJobScheduler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Threading;
using System.Threading.Tasks;
using Cronos;
using Hangfire;
using IOKode.OpinionatedFramework.Ensuring;
using IOKode.OpinionatedFramework.Jobs;

namespace IOKode.OpinionatedFramework.ContractImplementations.Hangfire.Jobs;

public class HangfireJobScheduler : IJobScheduler
{
public Task<ScheduledJob> ScheduleAsync(IJob job, CronExpression interval, CancellationToken cancellationToken)
{
var scheduledJob = new HangfireMutableScheduledJob(interval, job);
RecurringJob.AddOrUpdate(scheduledJob.Identifier.ToString(), () => InvokeAsync(job), interval.ToString);
return Task.FromResult<ScheduledJob>(scheduledJob);
}

public Task RescheduleAsync(ScheduledJob scheduledJob, CronExpression interval, CancellationToken cancellationToken)
{
Ensure.Type.IsAssignableTo(scheduledJob.GetType(), typeof(MutableScheduledJob))
.ElseThrowsIllegalArgument($"Type must be assignable to {nameof(MutableScheduledJob)} type.", nameof(scheduledJob));

RecurringJob.AddOrUpdate(scheduledJob.Identifier.ToString(), () => InvokeAsync(scheduledJob.Job), interval.ToString);
((MutableScheduledJob) scheduledJob).ChangeInterval(interval);

return Task.CompletedTask;
}

public Task UnscheduleAsync(ScheduledJob scheduledJob, CancellationToken cancellationToken)
{
RecurringJob.RemoveIfExists(scheduledJob.Identifier.ToString());
return Task.CompletedTask;
}

/// <summary>
/// This method is not intended to be called directly in application code.
/// Exists to allow Hangfire to serialize and deserialize the job object
/// that it will receive as an argument. This ensures that the job
/// can be processed correctly during execution.
/// </summary>
/// <remarks>
/// This method must be public because it is used by Hangfire during the deserialization
/// and execution of scheduled tasks. Hangfire requires that methods to be invoked
/// are publicly accessible to resolve them when deserializing the previously generated expression.
/// </remarks>
public async Task InvokeAsync(IJob job)
{
await job.InvokeAsync(default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using Cronos;
using IOKode.OpinionatedFramework.Jobs;

namespace IOKode.OpinionatedFramework.ContractImplementations.Hangfire.Jobs;

public class HangfireMutableScheduledJob : MutableScheduledJob
{
public HangfireMutableScheduledJob(CronExpression interval, IJob job) : base(interval, job)
{
}

public HangfireMutableScheduledJob(CronExpression interval, IJob job, Guid id) : base(interval, job, id)
{
}
}
18 changes: 18 additions & 0 deletions src/ContractImplementations.Hangfire/Jobs/ServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using Hangfire;
using IOKode.OpinionatedFramework.Bootstrapping;
using IOKode.OpinionatedFramework.Jobs;
using Microsoft.Extensions.DependencyInjection;

namespace IOKode.OpinionatedFramework.ContractImplementations.Hangfire.Jobs;

public static class ServiceExtensions
{
public static void AddHangfireJobsImplementations(this IOpinionatedServiceCollection services, Action<IGlobalConfiguration> configuration)
{
services.AddSingleton<IJobEnqueuer, HangfireJobEnqueuer>();
services.AddSingleton<IJobScheduler, HangfireJobScheduler>();

configuration.Invoke(GlobalConfiguration.Configuration);
}
}
19 changes: 19 additions & 0 deletions src/Foundation/Jobs/ScheduledJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@

namespace IOKode.OpinionatedFramework.Jobs;

/// <summary>
/// Represents the core details of a job that needs to be scheduled.
/// </summary>
/// <remarks>
/// This abstract class is designed to encapsulate essential aspects of a job that is to be scheduled,
/// its primary purpose is to serve as a foundational structure for managing metadata associated with
/// scheduled jobs in scheduling frameworks.
/// This class itself is not intended to represent the actual execution logic of the job,
/// but rather its scheduling details.
/// </remarks>
public abstract class ScheduledJob
{
public CronExpression Interval { get; protected set; }
Expand All @@ -24,6 +34,15 @@ protected void SetInterval(CronExpression interval)
}
}

/// <summary>
/// Extends <see cref="ScheduledJob"/> by allowing modifications to the execution interval.
/// </summary>
/// <remarks>
/// It is designed to support scenarios where the scheduling
/// details of a job may need to change after it has been initially defined.
/// Like its base class, this class focuses on managing the metadata and scheduling information for a job,
/// rather than its actual execution logic.
/// </remarks>
public abstract class MutableScheduledJob : ScheduledJob
{
public void ChangeInterval(CronExpression interval)
Expand Down
14 changes: 14 additions & 0 deletions src/IOKode.OpinionatedFramework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DefaultBootstrapping", "Def
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCoreIntegrations", "AspNetCoreIntegrations", "{038D9778-AA71-45A0-A33B-AFD50623285C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContractImplementations.Hangfire", "ContractImplementations.Hangfire\ContractImplementations.Hangfire.csproj", "{67CC3E42-1F98-439F-A83F-7F9B5B54CF86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Hangfire", "Tests.Hangfire\Tests.Hangfire.csproj", "{4CFA733F-0697-47FF-831D-908530C40929}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContractImplementations.TaskRunJobs", "ContractImplementations.TaskRunJobs\ContractImplementations.TaskRunJobs.csproj", "{9F7DB6F3-1AD9-4B31-9D02-1AD49224A4A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.TaskRunJobs", "Tests.TaskRunJobs\Tests.TaskRunJobs.csproj", "{1F0291CD-0C3D-414F-B7D4-8062A8D62F41}"
Expand Down Expand Up @@ -149,6 +153,14 @@ Global
{4DFA2F5C-12DA-41F8-853F-3B6CF322C854}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DFA2F5C-12DA-41F8-853F-3B6CF322C854}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DFA2F5C-12DA-41F8-853F-3B6CF322C854}.Release|Any CPU.Build.0 = Release|Any CPU
{67CC3E42-1F98-439F-A83F-7F9B5B54CF86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67CC3E42-1F98-439F-A83F-7F9B5B54CF86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67CC3E42-1F98-439F-A83F-7F9B5B54CF86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67CC3E42-1F98-439F-A83F-7F9B5B54CF86}.Release|Any CPU.Build.0 = Release|Any CPU
{4CFA733F-0697-47FF-831D-908530C40929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CFA733F-0697-47FF-831D-908530C40929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CFA733F-0697-47FF-831D-908530C40929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CFA733F-0697-47FF-831D-908530C40929}.Release|Any CPU.Build.0 = Release|Any CPU
{9F7DB6F3-1AD9-4B31-9D02-1AD49224A4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F7DB6F3-1AD9-4B31-9D02-1AD49224A4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F7DB6F3-1AD9-4B31-9D02-1AD49224A4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -211,6 +223,8 @@ Global
{A5A2C6D2-7069-4EF7-9391-0FA04924031A} = {C33ED8EA-76EF-4985-9055-12D9B668E210}
{AB7AE28D-9F0D-4528-B8FD-FCC4263F2227} = {C33ED8EA-76EF-4985-9055-12D9B668E210}
{F034D634-3141-4E98-97F1-BB9DF22C4917} = {756C3C1A-72CD-4CCE-BD8D-977F2980D1B6}
{67CC3E42-1F98-439F-A83F-7F9B5B54CF86} = {C33ED8EA-76EF-4985-9055-12D9B668E210}
{4CFA733F-0697-47FF-831D-908530C40929} = {756C3C1A-72CD-4CCE-BD8D-977F2980D1B6}
{9F7DB6F3-1AD9-4B31-9D02-1AD49224A4A6} = {C33ED8EA-76EF-4985-9055-12D9B668E210}
{1F0291CD-0C3D-414F-B7D4-8062A8D62F41} = {756C3C1A-72CD-4CCE-BD8D-977F2980D1B6}
{8E3B899D-50FA-4166-BD78-582E8E2BB16B} = {756C3C1A-72CD-4CCE-BD8D-977F2980D1B6}
Expand Down
Loading

0 comments on commit 0df3079

Please sign in to comment.