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 theBackgroundJob
andBackgroundJobArgs
classes between the web and job executor applications. - Install the
Volo.Abp.BackgroundJobs.Abstractions
package to theSeparateBackgroundJob.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:
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 theBackgroundJobExecutor
project. - Install the
Volo.Abp.BackgroundJobs.EntityFrameworkCore
andVolo.Abp.EntityFrameworkCore.SqlServer
packages to theBackgroundJobExecutor
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:
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:
Comments
Enis Necipoğlu 57 weeks ago
Great post! Thanks
805542231@qq.com 54 weeks ago
你好有个问题请教一下,按照如下连接配置项目。https://docs.abp.io/en/abp/latest/Background-Workers-Quartz发现内存泄露,经过排查,在最终的Execute方法中什么也不写,内存还是增长下不来。请教下是框架的原因吗?
Shiwei Liang 54 weeks ago
Please create an issue on the Github, thanks
Larisabrownb 52 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 52 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 ...