Skip to main content
Version: Next

12. Public API

The framework's entire public surface is 36 Stable types (0 Experimental, 0 Deprecated), frozen under the LTS commitment described in §1.6. Every type below was confirmed directly against current source, grouped by package. Namespaces are Shumoul.BackgroundJobs.{Package}.* unless noted.

12.1 Shumoul.BackgroundJobs.Contracts — 11 public types

Enums (Shumoul.BackgroundJobs.Contracts.Enums)

TypeValues
BackgroundJobStatusPending=1, Processing=2, Succeeded=3, Failed=4, Cancelled=5, AwaitingRetry=6
BackgroundJobTriggerTypeFireAndForget=1, Delayed=2, Recurring=3, Continuation=4
BackgroundJobPriorityLow=1, Normal=2, High=3, Critical=4
BackgroundJobFailureCategoryTransient=1, Permanent=2, Cancelled=3

Records / DTOs (Shumoul.BackgroundJobs.Contracts.DTOs)

TypeKindSignature
BackgroundJobIdreadonly record struct(string Value) — implicit operator string; overridden ToString()
BackgroundJobTenantInfosealed record(Guid TenantId, string? Identifier, bool IsRootTenant)
BackgroundJobLockKeysealed record(string MethodName, Guid? TenantId) — computed string Resource"backgroundjob:{MethodName}[:{TenantId}]"
BackgroundJobRetryPolicysealed record(int MaxAttempts, IReadOnlyList<TimeSpan>? RetryIntervals = null, bool RetryOnlyOnTransientFailures = false)
BackgroundJobScheduleRequestsealed record(string? Queue = null, BackgroundJobPriority Priority = Normal, BackgroundJobTenantInfo? TenantInfo = null, Guid? UserId = null, string? CorrelationId = null, BackgroundJobRetryPolicy? RetryPolicy = null)
BackgroundJobExecutionResultsealed record(bool Success, BackgroundJobStatus Status, DateTimeOffset CompletedOn, string? ErrorMessage = null, IReadOnlyDictionary<string,string>? Data = null). Static factories: Succeeded(data?), Failed(errorMessage), Cancelled(), RetryRequired(reason), PermanentFailure(errorMessage)
BackgroundJobContextsealed record(BackgroundJobId JobId, string? RecurringJobId, BackgroundJobTenantInfo? Tenant, Guid? UserId, string? Queue, BackgroundJobTriggerType TriggerType, string? CorrelationId, BackgroundJobPriority Priority = Normal)

12.2 Shumoul.BackgroundJobs.Abstractions — 14 public types

Runtime interfaces (...Abstractions.Interfaces)

TypeSignaturePurpose
IBackgroundJobTask RunAsync(IBackgroundJobExecutionContext context, CancellationToken ct)The contract every job-specific adapter implements — the single method Background Jobs Framework calls to run a job
IBackgroundJobExecutorTask<BackgroundJobExecutionResult> ExecuteAsync(IBackgroundJobExecutionContext context, CancellationToken ct = default)Wraps a single IBackgroundJob invocation and turns its outcome into a BackgroundJobExecutionResult
IBackgroundJobExecutionPipelineTask<BackgroundJobExecutionResult> RunAsync(IBackgroundJobExecutionContext context, Func<IBackgroundJobExecutionContext, CancellationToken, Task<BackgroundJobExecutionResult>> next, CancellationToken ct = default)The orchestration entry point every Hangfire bridge calls
IBackgroundJobExecutionContextRead-only: JobId, RecurringJobId, TenantId, UserId, Queue, TriggerType, CorrelationId, Priority, CancellationTokenEverything a job needs to know about the execution it's running inside — see §11
IBackgroundJobSchedulerEnqueueAsync<TJob>, ScheduleAsync<TJob>, AddOrUpdateRecurringAsync<TJob>, RemoveRecurringAsync, TriggerRecurringAsync — all via Expression<Func<TJob,Task>> dispatchScheduling abstraction over Hangfire — see §10
IBackgroundJobTenantContextBackgroundJobTenantInfo? CurrentTenant { get; }, Guid? CurrentTenantId { get; }, bool TryGetCurrentTenant(out BackgroundJobTenantInfo? tenant)Tenant identity for the current execution — see §8
IBackgroundJobDistributedLockTask<IBackgroundJobLockHandle?> TryAcquireAsync(BackgroundJobLockKey key, TimeSpan timeout, CancellationToken ct = default)The distributed lock primitive — see §9
IBackgroundJobLockHandle: IAsyncDisposable; BackgroundJobLockKey Key { get; }, Task ReleaseAsync(CancellationToken ct = default)A held lock, released via DisposeAsync or explicit ReleaseAsync
IBackgroundJobFailureClassifierBackgroundJobFailureCategory Classify(Exception exception)Categorizes an exception as Transient, Permanent, or Cancelled

Adapter contracts (...Abstractions.Interfaces.Adapters)

TypeSignaturePurpose
IHangfireBackgroundJobAdapterEnqueueAsync<TJob>, ScheduleAsync<TJob>, AddOrUpdateRecurringAsync<TJob>, RemoveRecurringAsync, TriggerRecurringAsync (mirrors IBackgroundJobScheduler without BackgroundJobScheduleRequest)The port HangfireBackgroundJobAdapter implements
IBackgroundJobTenantResolverAdapterBackgroundJobTenantInfo? ResolveCurrentTenant()The port BackgroundJobTenantResolverAdapter implements
IBackgroundJobDistributedLockAdapterTask<IBackgroundJobLockHandle?> TryAcquireAsync(BackgroundJobLockKey key, TimeSpan timeout, CancellationToken ct = default)The port BackgroundJobDistributedLockAdapter implements
IBackgroundJobLoggingAdapterLogJobStarted(ctx), LogJobCompleted(ctx, result), LogJobFailed(ctx, exception)The port BackgroundJobLoggingAdapter implements
IBackgroundJobNotificationAdapterTask NotifyAsync(BackgroundJobContext context, BackgroundJobExecutionResult result, CancellationToken ct = default)The port BackgroundJobNotificationBoundaryAdapter implements — currently a permanent no-op, see §7.4

12.3 Shumoul.BackgroundJobs.Core — 3 public types

TypeKindSignature / notes
ServiceCollectionExtensions.AddBackgroundJobsFrameworkCore(IServiceCollection)static extension methodRegisters: IBackgroundJobScheduler → BackgroundJobScheduler (Transient), IBackgroundJobExecutionPipeline → BackgroundJobExecutionPipeline (Transient), IBackgroundJobTenantContext → BackgroundJobTenantContext (Scoped), IBackgroundJobDistributedLock → BackgroundJobsDistributedLockService (Transient), IBackgroundJobFailureClassifier → BackgroundJobFailureClassifier (Transient)
BackgroundJobExecutionContextsealed class (implements IBackgroundJobExecutionContext)Constructor: (BackgroundJobId jobId, BackgroundJobTriggerType triggerType, string? recurringJobId = null, Guid? tenantId = null, Guid? userId = null, string? queue = null, string? correlationId = null, BackgroundJobPriority priority = Normal, CancellationToken cancellationToken = default) — this exact signature is frozen under the LTS backward-compatibility policy
BackgroundJobExecutorsealed class (implements IBackgroundJobExecutor)Constructor (IBackgroundJob job) (throws on null); ExecuteAsync runs job.RunAsync then returns BackgroundJobExecutionResult.Succeeded()

Everything else in Core (BackgroundJobExecutionPipeline, BackgroundJobScheduler, BackgroundJobTenantContext, BackgroundJobsDistributedLockService, BackgroundJobFailureClassifier, BackgroundJobExecutionLifetime) is internal sealed — deliberately not part of the public surface. See §4 and §6 for what these internal types do.

12.4 Shumoul.BackgroundJobs.Adapters — 7 public types

TypeKindConstructor / notes
AdapterServiceCollectionExtensions.AddBackgroundJobsAdapters(IServiceCollection, Action<BackgroundJobAdapterOptions>?)static extension methodRegisters all 5 adapters as Transient; must be called after AddHangfire(), MultiTenancy registration, and AddLogging(), and after AddShumoulBackgroundJobsFramework()
BackgroundJobAdapterOptionssealed classstring RootTenantIdentifier { get; set; } = "000111"
HangfireBackgroundJobAdaptersealed class (implements IHangfireBackgroundJobAdapter)(IBackgroundJobClient client, IRecurringJobManager recurringJobManager) — the only class in the framework importing Hangfire.* types
BackgroundJobTenantResolverAdaptersealed class (implements IBackgroundJobTenantResolverAdapter)(IMultiTenantContextAccessor contextAccessor, IOptions<BackgroundJobAdapterOptions> options) — reads contextAccessor.MultiTenantContext.TenantInfo, parses the tenant Guid, compares Identifier against RootTenantIdentifier (ordinal, case-insensitive)
BackgroundJobDistributedLockAdaptersealed class (implements IBackgroundJobDistributedLockAdapter)(JobStorage storage) — uses storage.GetConnection().AcquireDistributedLock(key.Resource, timeout); returns null on DistributedLockTimeoutException
BackgroundJobLoggingAdaptersealed class (implements IBackgroundJobLoggingAdapter)(ILogger<BackgroundJobLoggingAdapter> logger)
BackgroundJobNotificationBoundaryAdaptersealed class (implements IBackgroundJobNotificationAdapter)NotifyAsyncTask.CompletedTask — confirmed no-op in current source

HangfireLockHandle (implements IBackgroundJobLockHandle) is internal sealed — wraps the Hangfire lock's IDisposable and IStorageConnection, using Interlocked.Exchange for idempotent double-dispose safety.

12.5 Shumoul.BackgroundJobs (Entry Point) — 1 public type

TypeKindSignature
BackgroundJobsModuleExtensions.AddShumoulBackgroundJobsFramework(IServiceCollection)static extension methodSingle-line body: => services.AddBackgroundJobsFrameworkCore();

12.6 What is not part of the public API

An IBackgroundJobTrackingAdapter is mentioned in three code comments across the repository (a docstring on the entry-point extension method, and two comments inside Core) as forward-looking, planned work. No such interface, class, or file exists anywhere in the repository today. Do not write integration code against it, and do not treat it as an existing part of this framework's contract — it is a comment about possible future work, not a shipped API.

12.7 Usage example — a minimal new job

// 1. Implement IBackgroundJob — this is the only interface a new job needs to implement directly.
public sealed class MyReportCleanupJob : IBackgroundJob
{
private readonly IMyReportCleanupWorker _worker; // existing ERP business service, unchanged

public MyReportCleanupJob(IMyReportCleanupWorker worker) => _worker = worker;

public Task RunAsync(IBackgroundJobExecutionContext context, CancellationToken ct)
=> _worker.CleanupOldReportsAsync(ct); // adapter calls exactly one worker method, nothing else
}

// 2. Bridge it to Hangfire, in the ERP host, via the pipeline.
public interface IMyReportCleanupPipelineJob : ITransientService
{
Task RunAsync(CancellationToken ct);
}

public sealed class MyReportCleanupPipelineJob : IMyReportCleanupPipelineJob
{
private readonly IBackgroundJobExecutionPipeline _pipeline;
private readonly IMyReportCleanupWorker _worker;

public MyReportCleanupPipelineJob(IBackgroundJobExecutionPipeline pipeline, IMyReportCleanupWorker worker)
{
_pipeline = pipeline;
_worker = worker;
}

public Task RunAsync(CancellationToken ct)
{
var context = new BackgroundJobExecutionContext(
jobId: new BackgroundJobId(Guid.NewGuid().ToString()),
triggerType: BackgroundJobTriggerType.Recurring,
recurringJobId: "my-report-cleanup-pipeline",
cancellationToken: ct);

var executor = new BackgroundJobExecutor(new MyReportCleanupJob(_worker));
return _pipeline.RunAsync(context, (ctx, token) => executor.ExecuteAsync(ctx, token), ct);
}
}

// 3. Register it in ApplicationBuilderExtensions.cs, same cutover pattern as every other migration:
RecurringJob.AddOrUpdate<IMyReportCleanupPipelineJob>(
"my-report-cleanup-pipeline",
job => job.RunAsync(CancellationToken.None),
"0 4 * * *",
new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });

This mirrors exactly the shape of all six completed migrations — see §14 — Job Migration Guide.