Moving Background Job Execution To A Separate Application

In this article, I will show you how to move the background job execution to a separate application.

Here are some benefits of doing this:

  • If your background jobs consume high system resources (CPU, RAM or Disk), then you can deploy that background application to a dedicated server so it won't affect your application's performance.
  • You can scale your background job application independently from your web application. For example, you can deploy multiple instances of your background job application to a Kubernetes cluster and scale it easily.

Here are some disadvantages of doing this:

  • You need to deploy and maintain at least two applications instead of one.
  • You need to implement a mechanism to share the common code between your applications. For example, you can create a shared project and add it to your applications as a project reference.

Source code

You can find the source code of the application at abpframework/abp-samples.

You can check the PR to see the changes step by step: abpframework/abp-samples#250

Creating the Web Application

First, we need to create a new web application using the ABP CLI:

abp new SeparateBackgroundJob -t app
  • Create a shared project named SeparateBackgroundJob.Common.Shared to share the BackgroundJob and BackgroundJobArgs classes between the web and job executor applications.
  • Install the Volo.Abp.BackgroundJobs.Abstractions package to the SeparateBackgroundJob.Common.Shared project.

Add the SeparateBackgroundJobCommonSharedModule class to the SeparateBackgroundJob.Common.Shared project:

[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
public class SeparateBackgroundJobCommonSharedModule : AbpModule
{
}

Add the MyReportJob and MyReportJobArgs classes to the SeparateBackgroundJob.Common.Shared project:

public class MyReportJob : AsyncBackgroundJob<MyReportJobArgs>, ITransientDependency
{
    public override Task ExecuteAsync(MyReportJobArgs args)
    {
        Logger.LogInformation("Executing MyReportJob with args: {0}", args.Content);
        return Task.CompletedTask;
    }
}

public class MyReportJobArgs
{
    public string? Content { get; set; }
}

Add the SeparateBackgroundJob.Common.Shared project reference to the SeparateBackgroundJob.Domain project and add SeparateBackgroundJobCommonSharedModule to the DependsOn attribute of the SeparateBackgroundJobDomainModule class:

[DependsOn(
    typeof(SeparateBackgroundJobDomainSharedModule),
    typeof(AbpAuditLoggingDomainModule),
    typeof(AbpBackgroundJobsDomainModule),
    typeof(AbpFeatureManagementDomainModule),
    typeof(AbpIdentityDomainModule),
    typeof(AbpOpenIddictDomainModule),
    typeof(AbpPermissionManagementDomainOpenIddictModule),
    typeof(AbpPermissionManagementDomainIdentityModule),
    typeof(AbpSettingManagementDomainModule),
    typeof(AbpTenantManagementDomainModule),
    typeof(AbpEmailingModule),
    typeof(SeparateBackgroundJobCommonSharedModule) //Add this line
)]
public class SeparateBackgroundJobDomainModule : AbpModule

Open the Index.cshtml and replace the content with the following code:

@page
@using Microsoft.AspNetCore.Mvc.Localization
@using SeparateBackgroundJob.Localization
@using Volo.Abp.Users
@model SeparateBackgroundJob.Web.Pages.IndexModel
@inject IHtmlLocalizer<SeparateBackgroundJobResource> L
@inject ICurrentUser CurrentUser

@section styles {
    <abp-style src="/Pages/Index.css"/>
}

@section scripts {
    <abp-script src="/Pages/Index.js"/>
}

<div class="container">
    <abp-card>
        <abp-card-header>
            <abp-card-title>
                Add NEW BACKGROUND JOB
            </abp-card-title>
        </abp-card-header>
        <abp-card-body>
            <form id="NewItemForm" method="post" class="row row-cols-lg-auto g-3 align-items-center">
                <div class="col-12">
                    <div class="input-group">
                        <input id="ReportContent" required name="ReportContent" type="text" class="form-control" placeholder="enter text...">
                    </div>
                </div>
                <div class="col-12">
                    <button type="submit" class="btn btn-primary">Add</button>
                </div>
            </form>
        </abp-card-body>
    </abp-card>
</div>

Open the Index.cshtml.cs and replace the content with the following code:

public class IndexModel : SeparateBackgroundJobPageModel
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    [BindProperty(SupportsGet = true)]
    public string? ReportContent { get; set; }

    public IndexModel(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public void OnGet()
    {

    }
    
    public async Task OnPostAsync()
    {
        await _backgroundJobManager.EnqueueAsync(new MyReportJobArgs
        {
            Content = ReportContent
        });
        
        Alerts.Success("Job is queued!");
    }
}

Run the application and navigate to the home page. You should see the following page:

1

When you enter some text and click the Add button, the job will be queued and executed in the web application:

Creating the Console Application

Now we split the background job execution to a separate console application.

Open the SeparateBackgroundJobWebModule class to disable the background job execution in the web application:

public class SeparateBackgroundJobWebModule : AbpModule
{
    ....
        
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        ...
            
        //Disable background job execution in the web application
        Configure<AbpBackgroundJobOptions>(options =>
        {
            options.IsJobExecutionEnabled = false;
        });
    }
    
    ...
}
  • Create a new console application using the ABP CLI:
abp new BackgroundJobExecutor -t console
  • Add the BackgroundJobExecutor project to the solution of the web application.
  • Add the SeparateBackgroundJob.Common.Shared project reference to the BackgroundJobExecutor project.
  • Install the Volo.Abp.BackgroundJobs.EntityFrameworkCore and Volo.Abp.EntityFrameworkCore.SqlServer packages to the BackgroundJobExecutor project.

Update the BackgroundJobExecutorModule class as follows:

[DependsOn(
    typeof(AbpAutofacModule),
    typeof(AbpBackgroundJobsEntityFrameworkCoreModule),
    typeof(AbpEntityFrameworkCoreSqlServerModule),
    typeof(SeparateBackgroundJobCommonSharedModule)
)]
public class BackgroundJobExecutorModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpDbContextOptions>(options =>
        {
            options.UseSqlServer();
        });
    }
    
    ....
}

Open the appsettings.json file to configure the connection string:

{
  "ConnectionStrings": {
    "AbpBackgroundJobs": "Server=(LocalDb)\\MSSQLLocalDB;Database=SeparateBackgroundJob;Trusted_Connection=True"
  }
}

You must use the same connection string for the web application, AbpBackgroundJobs is the default connection string name for the background job module.

The solution structure should look like this:

solution

Now, run the web and console application. When you enter some text and click the Add button, the job will be queued and executed in the console application:

2

abp background-job
Enis Necipoğlu 28 weeks ago

Great post! Thanks

805542231@qq.com 25 weeks ago

你好有个问题请教一下,按照如下连接配置项目。https://docs.abp.io/en/abp/latest/Background-Workers-Quartz发现内存泄露,经过排查,在最终的Execute方法中什么也不写,内存还是增长下不来。请教下是框架的原因吗?

Shiwei Liang 25 weeks ago

Please create an issue on the Github, thanks

Larisabrownb 24 weeks ago

You may decide to move the processing to the different process from the main application. For example, your web application will only enqueue background jobs, ...Background jobs can be executed without requiring user interaction--the application can start the job and then continue to process interactive ... In your documantation you separated Background Jobs and Background Workers. Backgorund jobs are persisted in the table AbpBack groundJobs ... When I first began implementing background jobs I found myself moving my code into an appropriate background class whether it was in app or ...

Larisabrownb 24 weeks ago

You may decide to move the processing to the different process from the main application. For example, your web application will only enqueue background jobs, ...Background jobs can be executed without requiring user interaction--the application can start the job and then continue to process interactive ... In your documantation you separated Background Jobs and Background Workers. Backgorund jobs are persisted in the table AbpBack groundJobs ... When I first began implementing background jobs I found myself moving my code into an appropriate background class whether it was in app or ...