Skip to main content

IJob<T> Interface

The core abstraction for payload-based background jobs. Implement this interface to define a unit of work that processes a typed payload in the background.

Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll

public interface IJob<in TPayload> where TPayload : class
{
Task ExecuteAsync(TPayload payload, JobContext context);
}

ExecuteAsync​

Task ExecuteAsync(TPayload payload, JobContext context);
ParameterTypeDescription
payloadTPayloadThe deserialized payload for this job.
contextJobContextExecution context with job metadata, cancellation, and logging.

Contract:

  • Success — when ExecuteAsync completes without throwing, the job is marked as Succeeded.
  • Failure — when ExecuteAsync throws any exception, the job is marked as failed. If retries remain, it is rescheduled with exponential backoff. If all attempts are exhausted, it moves to DeadLetter.
  • Cancellation — throw OperationCanceledException (or let it propagate from context.CancellationToken) to signal cancellation. The worker treats this the same as a failure for retry purposes.

Payload constraints​

The TPayload type parameter has a class constraint — it must be a reference type. The payload is serialized to JSON when the job is enqueued and deserialized when the worker picks it up.

Serialization uses System.Text.Json with snake_case naming (JsonNamingPolicy.SnakeCaseLower), case-insensitive deserialization, and null-value ignoring. Your payload class should be a simple POCO or record:

public record NewUserEvent
{
public string Email { get; init; } = "";
public string Name { get; init; } = "";
public string UserId { get; init; } = "";
}
tip

Use record types with init properties for payloads. They are immutable, concise, and serialize cleanly.

Dependency injection​

Job classes are resolved from the DI container on every execution. The SDK registers each discovered IJob<T> implementation as transient, so you get a fresh instance for each job run. Constructor injection works normally:

public class SendWelcomeEmail : IJob<NewUserEvent>
{
private readonly IEmailService _email;
private readonly ILogger<SendWelcomeEmail> _logger;

public SendWelcomeEmail(IEmailService email, ILogger<SendWelcomeEmail> logger)
{
_email = email;
_logger = logger;
}

public async Task ExecuteAsync(NewUserEvent payload, JobContext ctx)
{
await _email.SendAsync(payload.Email, "Welcome!", ctx.CancellationToken);
ctx.Logger.LogInformation("Welcome email sent to {Email}", payload.Email);
}
}

The SDK discovers job classes by scanning assemblies at startup. By default it scans the entry assembly. To scan additional assemblies, set JobAssemblies in ZeridionFlareOptions.

Configuring defaults​

Apply the [JobConfig] attribute to set class-level defaults for queue, retries, and timeout:

[JobConfig(Queue = "email", MaxAttempts = 5, TimeoutSeconds = 120)]
public class SendWelcomeEmail : IJob<NewUserEvent>
{
// ...
}

These defaults can be overridden per-call with JobOptions. See Option precedence for the full resolution order.

Usage example​

public class GenerateThumbnail : IJob<ImageUploadEvent>
{
private readonly IImageProcessor _processor;
private readonly IBlobStorage _storage;

public GenerateThumbnail(IImageProcessor processor, IBlobStorage storage)
{
_processor = processor;
_storage = storage;
}

public async Task ExecuteAsync(ImageUploadEvent payload, JobContext ctx)
{
ctx.Logger.LogInformation("Generating thumbnail for {ImageId}, attempt {Attempt}",
payload.ImageId, ctx.AttemptNumber);

var image = await _storage.DownloadAsync(payload.OriginalUrl, ctx.CancellationToken);
var thumbnail = await _processor.ResizeAsync(image, 200, 200, ctx.CancellationToken);
await _storage.UploadAsync($"thumbs/{payload.ImageId}.jpg", thumbnail, ctx.CancellationToken);
}
}

public record ImageUploadEvent
{
public string ImageId { get; init; } = "";
public string OriginalUrl { get; init; } = "";
}

See also​