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)
| Type | Values |
|---|---|
BackgroundJobStatus | Pending=1, Processing=2, Succeeded=3, Failed=4, Cancelled=5, AwaitingRetry=6 |
BackgroundJobTriggerType | FireAndForget=1, Delayed=2, Recurring=3, Continuation=4 |
BackgroundJobPriority | Low=1, Normal=2, High=3, Critical=4 |
BackgroundJobFailureCategory | Transient=1, Permanent=2, Cancelled=3 |
Records / DTOs (Shumoul.BackgroundJobs.Contracts.DTOs)
| Type | Kind | Signature |
|---|---|---|
BackgroundJobId | readonly record struct | (string Value) — implicit operator string; overridden ToString() |
BackgroundJobTenantInfo | sealed record | (Guid TenantId, string? Identifier, bool IsRootTenant) |
BackgroundJobLockKey | sealed record | (string MethodName, Guid? TenantId) — computed string Resource → "backgroundjob:{MethodName}[:{TenantId}]" |
BackgroundJobRetryPolicy | sealed record | (int MaxAttempts, IReadOnlyList<TimeSpan>? RetryIntervals = null, bool RetryOnlyOnTransientFailures = false) |
BackgroundJobScheduleRequest | sealed record | (string? Queue = null, BackgroundJobPriority Priority = Normal, BackgroundJobTenantInfo? TenantInfo = null, Guid? UserId = null, string? CorrelationId = null, BackgroundJobRetryPolicy? RetryPolicy = null) |
BackgroundJobExecutionResult | sealed 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) |
BackgroundJobContext | sealed 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)
| Type | Signature | Purpose |
|---|---|---|
IBackgroundJob | Task RunAsync(IBackgroundJobExecutionContext context, CancellationToken ct) | The contract every job-specific adapter implements — the single method Background Jobs Framework calls to run a job |
IBackgroundJobExecutor | Task<BackgroundJobExecutionResult> ExecuteAsync(IBackgroundJobExecutionContext context, CancellationToken ct = default) | Wraps a single IBackgroundJob invocation and turns its outcome into a BackgroundJobExecutionResult |
IBackgroundJobExecutionPipeline | Task<BackgroundJobExecutionResult> RunAsync(IBackgroundJobExecutionContext context, Func<IBackgroundJobExecutionContext, CancellationToken, Task<BackgroundJobExecutionResult>> next, CancellationToken ct = default) | The orchestration entry point every Hangfire bridge calls |
IBackgroundJobExecutionContext | Read-only: JobId, RecurringJobId, TenantId, UserId, Queue, TriggerType, CorrelationId, Priority, CancellationToken | Everything a job needs to know about the execution it's running inside — see §11 |
IBackgroundJobScheduler | EnqueueAsync<TJob>, ScheduleAsync<TJob>, AddOrUpdateRecurringAsync<TJob>, RemoveRecurringAsync, TriggerRecurringAsync — all via Expression<Func<TJob,Task>> dispatch | Scheduling abstraction over Hangfire — see §10 |
IBackgroundJobTenantContext | BackgroundJobTenantInfo? CurrentTenant { get; }, Guid? CurrentTenantId { get; }, bool TryGetCurrentTenant(out BackgroundJobTenantInfo? tenant) | Tenant identity for the current execution — see §8 |
IBackgroundJobDistributedLock | Task<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 |
IBackgroundJobFailureClassifier | BackgroundJobFailureCategory Classify(Exception exception) | Categorizes an exception as Transient, Permanent, or Cancelled |
Adapter contracts (...Abstractions.Interfaces.Adapters)
| Type | Signature | Purpose |
|---|---|---|
IHangfireBackgroundJobAdapter | EnqueueAsync<TJob>, ScheduleAsync<TJob>, AddOrUpdateRecurringAsync<TJob>, RemoveRecurringAsync, TriggerRecurringAsync (mirrors IBackgroundJobScheduler without BackgroundJobScheduleRequest) | The port HangfireBackgroundJobAdapter implements |
IBackgroundJobTenantResolverAdapter | BackgroundJobTenantInfo? ResolveCurrentTenant() | The port BackgroundJobTenantResolverAdapter implements |
IBackgroundJobDistributedLockAdapter | Task<IBackgroundJobLockHandle?> TryAcquireAsync(BackgroundJobLockKey key, TimeSpan timeout, CancellationToken ct = default) | The port BackgroundJobDistributedLockAdapter implements |
IBackgroundJobLoggingAdapter | LogJobStarted(ctx), LogJobCompleted(ctx, result), LogJobFailed(ctx, exception) | The port BackgroundJobLoggingAdapter implements |
IBackgroundJobNotificationAdapter | Task 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
| Type | Kind | Signature / notes |
|---|---|---|
ServiceCollectionExtensions.AddBackgroundJobsFrameworkCore(IServiceCollection) | static extension method | Registers: IBackgroundJobScheduler → BackgroundJobScheduler (Transient), IBackgroundJobExecutionPipeline → BackgroundJobExecutionPipeline (Transient), IBackgroundJobTenantContext → BackgroundJobTenantContext (Scoped), IBackgroundJobDistributedLock → BackgroundJobsDistributedLockService (Transient), IBackgroundJobFailureClassifier → BackgroundJobFailureClassifier (Transient) |
BackgroundJobExecutionContext | sealed 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 |
BackgroundJobExecutor | sealed 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
| Type | Kind | Constructor / notes |
|---|---|---|
AdapterServiceCollectionExtensions.AddBackgroundJobsAdapters(IServiceCollection, Action<BackgroundJobAdapterOptions>?) | static extension method | Registers all 5 adapters as Transient; must be called after AddHangfire(), MultiTenancy registration, and AddLogging(), and after AddShumoulBackgroundJobsFramework() |
BackgroundJobAdapterOptions | sealed class | string RootTenantIdentifier { get; set; } = "000111" |
HangfireBackgroundJobAdapter | sealed class (implements IHangfireBackgroundJobAdapter) | (IBackgroundJobClient client, IRecurringJobManager recurringJobManager) — the only class in the framework importing Hangfire.* types |
BackgroundJobTenantResolverAdapter | sealed class (implements IBackgroundJobTenantResolverAdapter) | (IMultiTenantContextAccessor contextAccessor, IOptions<BackgroundJobAdapterOptions> options) — reads contextAccessor.MultiTenantContext.TenantInfo, parses the tenant Guid, compares Identifier against RootTenantIdentifier (ordinal, case-insensitive) |
BackgroundJobDistributedLockAdapter | sealed class (implements IBackgroundJobDistributedLockAdapter) | (JobStorage storage) — uses storage.GetConnection().AcquireDistributedLock(key.Resource, timeout); returns null on DistributedLockTimeoutException |
BackgroundJobLoggingAdapter | sealed class (implements IBackgroundJobLoggingAdapter) | (ILogger<BackgroundJobLoggingAdapter> logger) |
BackgroundJobNotificationBoundaryAdapter | sealed class (implements IBackgroundJobNotificationAdapter) | NotifyAsync → Task.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
| Type | Kind | Signature |
|---|---|---|
BackgroundJobsModuleExtensions.AddShumoulBackgroundJobsFramework(IServiceCollection) | static extension method | Single-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.