/Horarium

Horarium is the .Net library to manage background jobs

Primary LanguageC#Apache License 2.0Apache-2.0

Horarium

Build Nuget

Horarium is an open source job scheduling .NET library with an easy to use API, that can be integrated within applications of any scale - from the smallest stand-alone application to the largest e-commerce system.

Horarium is fully based on an asynchronous work model, it allows you to run hundreds of parallel jobs within a single application instance. It supports jobs execution in distributed systems and uses MongoDB as a synchronization backend.

Horarium supports .NET Core/netstandard 2.0 and .NET Framework 4.6.2 and later.

Support Databases

Database Support
MongoDB Yes
In Memory Yes
PostgreSQL Not yet #6

Getting started

Add nuget-package Horarium

dotnet add package Horarium
dotnet add package Horarium.Mongo

Add job that implements interface IJob<T>

public class TestJob : IJob<int>
{
    public async Task Execute(int param)
    {
        Console.WriteLine(param);
        await Task.Run(() => { });
    }
}

Create HorariumServer and schedule TestJob

var horarium = new HorariumServer(new InMemoryRepository());
horarium.Start();
await horarium.Create<TestJob, int>(666)
        .Schedule();

Add to Asp.Net core application

Add nuget-package Horarium.AspNetCore

dotnet add package Horarium.AspNetCore

Add Horarium Server. This regiters Horarium as a hosted service, so .Net core runtime automatically starts and gracefully stops Horarium.

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddHorariumServer(MongoRepositoryFactory.Create("mongodb://localhost:27017/horarium"));
    //...
}

Inject interface IHorarium into Controller

private readonly IHorarium _horarium;

public HomeController(IHorarium horarium)
{
    _horarium = horarium;
}

[Route("api")]
public class HomeController : Controller
{
    [HttpPost]
    public async Task Run(int count)
    {
            await _horarium.Create<TestJob, int>(count)
                          .Schedule();
    }
}

Create Recurrent Job

Add job that implements interface IJobRecurrent

public class TestRecurrentJob : IJobRecurrent
    {
        public Task Execute()
        {
            Console.WriteLine("Run -" + DateTime.Now);
            return Task.CompletedTask;
        }
    }

Schedule TestRecurrentJob to run every 15 seconds

await horarium.CreateRecurrent<TestRecurrentJob>(Cron.SecondInterval(15))
                .Schedule();

Create sequence of jobs

Sometimes you need to create sequence of jobs, where every next job would run if and only if previous job succeeds. If any job of the sequence fails next jobs won't run

await horarium
    .Create<TestJob, int>(1) // 1-st job
    .Next<TestJob, int>(2) // 2-nd job
    .Next<TestJob, int>(3) // 3-rd job
    .Schedule();

Distributed Horarium

Distributed Scheme

Horarium has two types of workers: server and client. Server can run jobs and schedule new jobs, while client can only schedule new jobs.

Horarium guarantees that a job would run exactly once

Things to watch out for

Every Horarium instance consults MongoDB about new jobs to run every 100ms (default), thus creating some load on the DB server. This interval can be changed in HorariumSettings

If you want to decrease load, you can use job throttling that will automatically increase interval if there are no jobs available after certain attempts. To enable this feature, pass JobThrottleSettings to HorariumSettings with property UseJobThrottle set to true.

var settings = new HorariumSettings
{
    JobThrottleSettings = new JobThrottleSettings
    {
        UseJobThrottle = true
    }
};

For more information about configuration, see JobThrottleSettings

Using Horarium with SimpleInjector

To use Horarium with SimpleInjector one should implement its own IJobFactory, using Container from SimpleInjector. For example:

public class SimpleInjectorJobScopeFactory : IJobScopeFactory
{
    private readonly Container _container;

    public SimpleInjectorJobScopeFactory(Container container)
    {
        _container = container;
    }

    public IJobScope Create()
    {
        var scope = AsyncScopedLifestyle.BeginScope(_container);
        return new SimpleInjectorJobScope(scope);
    }
}

public class SimpleInjectorJobScope : IJobScope
{
    private readonly Scope _scope;

    public SimpleInjectorJobScope(Scope scope)
    {
        _scope = scope;
    }

    public object CreateJob(Type type)
    {
        return _scope.GetInstance(type);
    }

    public void Dispose()
    {
        _scope.Dispose();
    }
}

Then add HorariumServer (or HorariumClient):

container.RegisterSingleton<IHorarium>(() =>
{
    var settings = new HorariumSettings
    {
        JobScopeFactory = new SimpleInjectorJobScopeFactory(container),
        Logger = new YourHorariumLogger()
    };

    return new HorariumServer(jobRepository, settings);
});

In case of HorariumServer, don't forget to start it in your entypoint:

((HorariumServer) container.GetInstance<IHorarium>()).Start();

Failed repeat strategy for jobs

When a job fails, Horarium can handle this exception with the same strategy. By default, the job repeats 10 times with delays of 10 minutes, 20 minutes, 30 minutes and etc. You can override this strategy using IFailedRepeatStrategy interface.

Example of default DefaultRepeatStrategy implementation:

public class DefaultRepeatStrategy :IFailedRepeatStrategy
{
    public TimeSpan GetNextStartInterval(int countStarted)
    {
        const int increaseRepeat = 10;
        return TimeSpan.FromMinutes(increaseRepeat * countStarted);
    }
}

This class is called every time when a job fails, and it has to return TimeSpan of the next scheduled job run. To override default behavior globally, change settings in HorariumSettings

new HorariumSettings
{
    FailedRepeatStrategy = new CustomFailedRepeatStrategy(),
    MaxRepeatCount = 7
});

To override the default behavior for a particular job:

await horarium.Create<TestJob, int>(666)
    .MaxRepeatCount(5)
    .AddRepeatStrategy<DefaultRepeatStrategy>()
    .Schedule();

If you want to disable all repeats, just set MaxRepeatCount to 1

new HorariumSettings
{
    MaxRepeatCount = 1
});