Effects Composition¶
Background Jobs compose with the Deepstaging effects system via [EffectsModule], [Runtime], and [Uses] — the same pattern used by all other modules.
Declaring the Effects Module¶
Wrap IJobScheduler and IJobStore as effect modules:
using Deepstaging.Jobs;
using Deepstaging.Effects;
[EffectsModule(typeof(IJobScheduler))]
[EffectsModule(typeof(IJobStore))]
public sealed partial class JobEffects;
Composing with a Runtime¶
Add job effects to your runtime with [Uses]:
using Deepstaging.Effects;
[Runtime]
[Uses(typeof(JobEffects))]
[Uses(typeof(EmailEffects))]
public sealed partial class AppRuntime;
This generates typed accessors on the runtime so handlers can resolve IJobScheduler and IJobStore through the effect system.
Effect-Wrapped Job Scheduling¶
With the effects module registered, use the generated accessors in LINQ expressions:
// Enqueue a job for immediate execution
from jobId in JobEffects.JobScheduler.EnqueueAsync<AppRuntime>(
new SendWelcomeEmail(userId))
select jobId;
// Schedule a job for later
from jobId in JobEffects.JobScheduler.ScheduleAsync<AppRuntime>(
new SyncInventory("warehouse-1"),
DateTimeOffset.UtcNow.AddMinutes(30))
select jobId;
// Monitor dead-letter queue
from deadLettered in JobEffects.JobStore.GetDeadLetterAsync<AppRuntime>(limit: 50)
select deadLettered;
JobHandler Returning Eff<RT, Unit>¶
Job handlers return Eff<RT, Unit> to participate in the effect system. This gives handlers access to all capabilities composed into the runtime:
using Deepstaging.Jobs;
using LanguageExt;
public static class InventoryJobs
{
[JobHandler]
public static Eff<AppRuntime, Unit> Handle(SyncInventory job) =>
from inventory in WarehouseEffects.Warehouse
.GetInventoryAsync<AppRuntime>(job.WarehouseId)
from _ in WarehouseEffects.Warehouse
.SyncAsync<AppRuntime>(inventory)
>> AuditEffects.Audit.RecordAsync<AppRuntime>(
"inventory_synced", job.WarehouseId)
select unit;
}
Because the handler runs inside Eff<AppRuntime, Unit>, it can compose with any effect available on AppRuntime — audit logging, notifications, other job scheduling, etc.
Integration with Dispatch Pipeline¶
Job handlers integrate with the same dispatch pipeline used by command and query handlers. The generator:
- Discovers all
[JobHandler]methods in the assembly - Matches each handler's first parameter type to a
[BackgroundJob]record - Emits a strongly-typed dispatch table — the job type determines routing at compile time
- Wires retry, timeout, and dead-letter logic around the handler invocation
This means jobs benefit from the same compile-time safety as dispatch — no runtime reflection, no dictionary lookups, full type checking at build time.