Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a3c57f9
Add HTTP Client Wrapper module: retry, serialization, exception handling
ducdaiii Aug 15, 2025
ea773ec
Add HTTP Client Wrapper module: retry, serialization, exception handling
ducdaiii Aug 15, 2025
26a411d
[CodeFactor] Apply fixes
code-factor Aug 15, 2025
7219c84
Refactor: HTTP Client Wrapper module: retry, serialization, exception…
ducdaiii Aug 15, 2025
ebdc245
Refactor: HTTP Client Wrapper module: retry, serialization, exception…
ducdaiii Aug 15, 2025
748644e
feat(background-jobs): add full background job module with queue, wor…
ducdaiii Aug 16, 2025
5e3ddd4
[CodeFactor] Apply fixes
code-factor Aug 16, 2025
4e52bd5
Merge branch 'main' into feature/background-jobs
guibranco Dec 4, 2025
61dcd96
Merge branch 'main' into feature/background-jobs
guibranco Apr 21, 2026
cf71f8f
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
fef565a
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
b1fde96
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
72b4fb3
Merge branch 'main' into feature/background-jobs
guibranco May 23, 2026
9cec349
Merge branch 'main' into feature/background-jobs
guibranco May 23, 2026
cf5ac10
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
be52be4
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
a824470
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
2c15147
style: improve code readability and consistency +semver: minor
guibranco Jul 4, 2026
54e5770
refactor: extract methods for job fetching and processing
guibranco Jul 4, 2026
f4af0a2
refactor: improve code readability with formatting adjustments
guibranco Jul 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="JunitXml.TestLogger" Version="8.0.0" />
<PackageVersion Include="log4net" Version="3.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.1" />
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
Expand All @@ -35,4 +42,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
</ItemGroup>
</Project>
</Project>
13 changes: 13 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IBackgroundJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CrispyWaffle.BackgroundJobs.Core;

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

namespace CrispyWaffle.BackgroundJobs.Abstractions
{
/// <summary>
/// Low-level job representation (mostly used for in-memory workflows).
/// For persisted jobs, prefer IBackgroundJobHandler<TData> with registry-based activation.
/// </summary>
public interface IBackgroundJob
{
Task<JobResult> ExecuteAsync(CancellationToken cancellationToken);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'IBackgroundJob.cs'. Warning

Add a new line at the end of the file 'IBackgroundJob.cs'.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CrispyWaffle.BackgroundJobs.Core;

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

namespace CrispyWaffle.BackgroundJobs.Abstractions
{
/// <summary>
/// Typed handler used by persisted jobs. Implement this to allow DI-resolved handlers.
/// TData is the payload type stored as JSON in the job store.
/// </summary>
public interface IBackgroundJobHandler<TData>
{
Task<JobResult> HandleAsync(TData data, CancellationToken cancellationToken);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.
}
}
9 changes: 9 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IJobScheduler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.
{
public interface IJobScheduler
{
Task ScheduleAsync(string handlerName, object payload, TimeSpan delay, int maxAttempts = 3, JobPriority priority = JobPriority.Normal);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.

Task EnqueueAsync(string handlerName, object payload, int maxAttempts = 3, JobPriority priority = JobPriority.Normal);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'IJobScheduler.cs'. Warning

Add a new line at the end of the file 'IJobScheduler.cs'.
19 changes: 19 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IJobStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
{
public interface IJobStore
{
Task SaveAsync(JobEntity job, CancellationToken cancellationToken = default);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.

/// <summary>
/// Fetch the next available job that is due (ScheduledAt <= UtcNow) and Pending.
/// Implementation should atomically mark it as Processing to avoid double pick-up.
/// </summary>
Task<JobEntity?> FetchNextAsync(CancellationToken cancellationToken = default);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.

Task MarkCompletedAsync(Guid jobId, CancellationToken cancellationToken = default);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.

Task MarkFailedAsync(Guid jobId, string error, CancellationToken cancellationToken = default);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.

Task MarkRetryAsync(Guid jobId, DateTimeOffset? nextAttemptAt, int attemptCount, CancellationToken cancellationToken = default);

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Note

Use the overloading mechanism instead of the optional parameters.
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'IJobStore.cs'. Warning

Add a new line at the end of the file 'IJobStore.cs'.
47 changes: 47 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.
{
using System;

/// <summary>
/// Persistent representation of a job.
/// HandlerName identifies a registered handler; Payload is JSON.
/// </summary>
public class JobEntity
{
public Guid Id { get; set; } = Guid.NewGuid();

/// <summary>
/// Logical handler name registered in IJobHandlerRegistry.
/// </summary>
public string HandlerName { get; set; } = string.Empty;

/// <summary>
/// JSON payload (serialized) for the handler.
/// </summary>
public string Payload { get; set; } = string.Empty;

public JobPriority Priority { get; set; } = JobPriority.Normal;

public JobStatus Status { get; set; } = JobStatus.Pending;

/// <summary>
/// When the job becomes due for execution. Null means immediately.
/// </summary>
public DateTimeOffset? ScheduledAt { get; set; }

public int Attempt { get; set; } = 0;

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove this initialization to 'Attempt', the compiler will do that for you. Warning

Remove this initialization to 'Attempt', the compiler will do that for you.

public int MaxAttempt { get; set; } = 3;

public string? LastError { get; set; }

public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use a testable (date) time provider instead. Note

Use a testable (date) time provider instead.

public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Use a testable (date) time provider instead. Note

Use a testable (date) time provider instead.

/// <summary>
/// Optional delay in seconds between retries (used only as hint).
/// </summary>
public int RetryDelaySeconds { get; set; } = 0;

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove this initialization to 'RetryDelaySeconds', the compiler will do that for you. Warning

Remove this initialization to 'RetryDelaySeconds', the compiler will do that for you.
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'JobEntity.cs'. Warning

Add a new line at the end of the file 'JobEntity.cs'.
9 changes: 9 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
{
public enum JobPriority
{
High = 0,
Normal = 1,
Low = 2
}
}
11 changes: 11 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.
{
public enum JobStatus
{
Pending = 0,
Processing = 1,
Completed = 2,
Failed = 3,
Dead = 4
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'JobStatus.cs'. Warning

Add a new line at the end of the file 'JobStatus.cs'.
47 changes: 47 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Core/BackgroundJobQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using CrispyWaffle.BackgroundJobs.Abstractions;

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add or update the header of this file. Warning

Add or update the header of this file.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'. Note

Provide a 'ComVisible' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'. Note

Provide a 'CLSCompliant' attribute for assembly 'srcassembly.dll'.

Check notice

Code scanning / Sonarcsharp (reported by Codacy)

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'. Note

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
using System.Threading.Channels;

namespace CrispyWaffle.BackgroundJobs.Core
{
/// <summary>
/// Lightweight in-memory priority queue using Channel for signaling.
/// Priorities: lower int = higher priority.
/// This queue stores JobEntity (for uniformity with persistent store model).
/// </summary>
public class BackgroundJobQueue
{
private readonly object _lock = new();
private readonly PriorityQueue<JobEntity, int> _pq = new();
private readonly Channel<JobEntity> _signal = Channel.CreateUnbounded<JobEntity>(new UnboundedChannelOptions { SingleReader = false, SingleWriter = false });

public void Enqueue(JobEntity job)
{
lock (_lock)
{
_pq.Enqueue(job, (int)job.Priority);
}
// Always write to signal channel to notify consumers.
_ = _signal.Writer.WriteAsync(job);
}

public async Task<JobEntity?> DequeueAsync(CancellationToken cancellationToken)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Remove the 'Async' suffix to the name of this method. Warning

Remove the 'Async' suffix to the name of this method.
{
// Wait until a job is signaled and then pop from our priority queue.
try
{
await _signal.Reader.ReadAsync(cancellationToken);
}
catch (OperationCanceledException) { return null; }

lock (_lock)
{
if (_pq.TryDequeue(out var job, out _))
{
return job;
}
}

return null;
}
}
}

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Add a new line at the end of the file 'BackgroundJobQueue.cs'. Warning

Add a new line at the end of the file 'BackgroundJobQueue.cs'.
Loading
Loading