A durable job scheduling platform for dotnet
The in-memory store is useful for testing and development. It is not recommended for production use.
var jobba = new JobbaBuilder(serviceCollection, "sample")
.UsingInMemory();
To use the Mongo store, install the Jobba.Store.Mongo
package
dotnet add package Jobba.Store.Mongo
Then add the following to your JobbaBuilder
configuration, providing the connection string and a boolean to enable or
disable command logging. There is also an optional third parameter to allow for additional configuration of the Mongo
store using the JobbaMongoBuilder.cs.
var jobba = new JobbaBuilder(serviceCollection, "sample")
.UsingMongo(config.GetConnectionString("MongoDb"), false);
To use the EFCore store, install the Jobba.Store.EFCore
package
dotnet add package Jobba.Store.EFCore
There are currently 2 supported EF providers maintained by this library.
To use the SqlServer provider, install the Jobba.Store.EFCore.Sql
package
dotnet add package Jobba.Store.EFCore.Sql
Then add the following to your JobbaBuilder
configuration, providing the connection string, and optional actions to provide
additional configuration of the DbContextOptionsBuilder
, SqlServerDbContextOptionsBuilder
. and the JobbaEfBuilder.cs.
var jobba = new JobbaBuilder(serviceCollection, "sample")
.UsingSqlServer(config.GetConnectionString("SqlServer"),
options =>
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}, configureBuilder: jb => jb.WithDbInitializer());
To use the Sqlite provider, install the Jobba.Store.EFCore.Sqlite
package
dotnet add package Jobba.Store.EFCore.Sqlite
Then add the following to your JobbaBuilder
configuration, providing the connection string, and optional actions to provide
additional configuration of the DbContextOptionsBuilder
, SqliteDbContextOptionsBuilder
, and the JobbaEfBuilder.cs.
var jobba = new JobbaBuilder(serviceCollection, "sample")
.UsingSqlite(config.GetConnectionString("Sqlite"),
options =>
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}, configureBuilder: jb => jb.WithDbInitializer());
To use a different EF provider, you will need to manage the configuration and migrations yourself. You can refer to the SqlServer and Sqlite implementations for guidance. JobbaEfBuilderExtensions.cs
The following interfaces must be registered in the DI container for your custom store:
- IJobListStore.cs
- IJobProgressStore.cs
- IJobProgressStore.cs
- IJobProgressStore.cs
- IJobProgressStore.cs
You can refer to the InMemory implementation for an example of how to implement these interfaces.
Locking ensures that only one instance of a job is running at a time.
The in-memory lock is useful for testing and development. It is registered by default when using the JobbaBuilder
.
Only use in production if you have a single instance of your application.
This will use the lit-redis library which will allow for distributed locking across multiple instances of your application.
To use the Redis lock, install the Jobba.Redis
package
dotnet add package Jobba.Redis
Then add the following to your JobbaBuilder
configuration, providing your connection string.
var jobba = new JobbaBuilder(serviceCollection, "sample")
.UsingLitRedis(config.GetConnectionString("Redis"));
The following interface must be registered in the DI container for your custom lock:
You can refer to the InMemory implementation for an example of how to implement this interface.
Jobba publishes various events to allow for monitoring and logging of job progress.
The events published are:
- CancelJobEvent.cs
- JobWatchEvent.cs
- JobCancelledEvent.cs
- JobCompletedEvent.cs
- JobFaultedEvent.cs
- JobProgressEvent.cs
- JobRestartEvent.cs
- JobStartedEvent.cs
The in-memory event publisher is useful for testing and development. It is registered by default when using the JobbaBuilder
.
Only use in production if you have a single instance of your application.
This will use the MassTransit library to facilitate event publishing. By using MassTransit, you can publish events to a message broker such as RabbitMQ or Azure Service Bus allowing for distributed event handling.
The following interface must be registered in the DI container for your custom event publisher:
You must also register consumers for each event published by Jobba. You can refer to the JobbaMassTransitBuilderExtensions.cs for an example of how to implement this interface.
Check out the Sample project for a working example.
- Install the library
<ItemGroup>
<PackageReference Include="Jobba.Core" />
<PackageReference Include="Jobba.MassTransit" />
<PackageReference Include="Jobba.Redis" />
<PackageReference Include="Jobba.Store.Mongo" />
</ItemGroup>
or
dotnet add package Jobba.Core
dotnet add package Jobba.MassTransit
dotnet add package Jobba.Redis
dotnet add package Jobba.Store.Mongo
- Create a new
SampleJob
class that extendsAbstractJobBaseClass
. You can also create classes forJobState
andJobParameters
, or useobject
as a placeholder
using System;
using System.Threading;
using System.Threading.Tasks;
using Jobba.Core.Abstractions;
using Jobba.Core.Interfaces.Repositories;
using Jobba.Core.Models;
using Microsoft.Extensions.Logging;
namespace Jobba.Sample
{
public class SampleJobState
{
public int Tries { get; set; }
}
public class SampleJobParameters
{
public string Greeting { get; set; }
}
public class SampleJob : AbstractJobBaseClass<SampleJobParameters, SampleJobState> // or use `AbstractJobBaseClass<DefaultJobParams, DefaultJobState>` to not use state or parameters
{
private readonly ILogger<SampleJob> _logger;
public SampleJob(IJobProgressStore progressStore, ILogger<SampleJob> logger) : base(progressStore)
{
_logger = logger;
}
protected override async Task OnStartAsync(JobStartContext<SampleJobParameters, SampleJobState> jobStartContext, CancellationToken cancellationToken)
{
// implement your job's behavior
var tries = jobStartContext.JobState.Tries + 1;
_logger.LogInformation("Hey I'm trying! Tries: {Tries} {JobId} {Now}", tries, jobStartContext.JobId, DateTimeOffset.Now);
await LogProgressAsync(new SampleJobState { Tries = tries }, 50, jobStartContext.JobParameters.Greeting, cancellationToken);
await Task.Delay(100 * tries, cancellationToken);
if (tries < 10)
{
throw new Exception($"Haven't tried enough {tries}"); // jobba will retry if it encounters an exception
}
_logger.LogInformation("Now I'm done!");
}
public override string JobName => "Sample Job";
}
}
- In
Program.cs
, add the jobba configuration to theConfigureServices
callback inCreateHostBuilder
services
.AddLogging(o => o.AddSimpleConsole(c => c.TimestampFormat = "[yyyy-MM-dd HH:mm:ss] "))
.AddJobba(jobba =>
jobba.UsingMassTransit() // use MassTransit as an event bus
.UsingMongo("mongodb://localhost:27017/jobba-sample", false)
.UsingLitRedis("localhost:6379,defaultDatabase=0") // Use LitRedis for distributed locking
.AddJob<SampleJob, SampleJobParameters, SampleJobState>() // `AddJob<SampleJob, object, object>` if not using state or parameters
)
.AddJobbaSampleMassTransit("rabbitmq://guest:guest@localhost/")
.AddHostedService<SampleHostedService>();
This example uses
- MassTransit as an event bus
- Mongo as a data store
- LitRedis for distributed locking
- Make a service
SampleHostedService
that extendsBackgroundService
and injects anIJobScheduler
using System;
using System.Threading;
using System.Threading.Tasks;
using Jobba.Core.Interfaces;
using Jobba.Core.Models;
using Microsoft.Extensions.Hosting;
namespace Jobba.Sample
{
public class SampleHostedService : BackgroundService
{
private readonly IJobScheduler _jobScheduler;
public SampleHostedService(IJobScheduler jobScheduler)
{
_jobScheduler = jobScheduler;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// create and schedule your job
var request = new JobRequest<SampleJobParameters, SampleJobState>
{
Description = "A Sample Job",
JobParameters = new SampleJobParameters { Greeting = "Hello" },
JobType = typeof(SampleJob),
InitialJobState = new SampleJobState { Tries = 0 },
JobWatchInterval = TimeSpan.FromSeconds(10),
MaxNumberOfTries = 100
};
await _jobScheduler.ScheduleJobAsync(request, stoppingToken);
}
}
}
Create a new JobRequest
and schedule it with _jobScheduler
in SampleHostedService
's ExecuteAsync
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// create a job request
var request = new JobRequest<SampleJobParameters, SampleJobState>
{
Description = "A Sample Job",
JobParameters = new SampleJobParameters { Greeting = "Hello" },
JobType = typeof(SampleJob), // the class for the job to run
InitialJobState = new SampleJobState { Tries = 0 },
JobWatchInterval = TimeSpan.FromSeconds(10), // time to wait between retries if the job fails
MaxNumberOfTries = 100 // maximum number of times to retry when a job fails
};
// schedule it with the passed-in cancellation token
await _jobScheduler.ScheduleJobAsync(request, stoppingToken);
}
Use the job's ID and the provided cancellation token to cancel a scheduled or running job
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// create a job request
var request = new JobRequest<SampleJobParameters, SampleJobState>
{
Description = "A Sample Job",
JobParameters = new SampleJobParameters { Greeting = "Hello" },
JobType = typeof(SampleJob), // the class for the job to run
InitialJobState = new SampleJobState { Tries = 0 },
JobWatchInterval = TimeSpan.FromSeconds(10), // time to wait between retries if the job fails
MaxNumberOfTries = 100 // maximum number of times to retry when a job fails
};
// schedule it with the passed-in cancellation token
await _jobScheduler.ScheduleJobAsync(request, stoppingToken);
// wait a second and cancel the job
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
await _jobScheduler.CancelJobAsync(request.Id, stoppingToken);
}