زمانبندی Background Jobها با Quartz در NET. (مفاهیم پیشرفته) ⏱️
اکثر اپلیکیشنهای ASP.NET Core به پردازش پسزمینه (background processing) نیاز دارند - از ارسال ایمیلهای یادآوری گرفته تا اجرای تسکهای پاکسازی. با اینکه راههای زیادی برای پیادهسازی background jobها وجود دارد، Quartz.NET با قابلیتهای زمانبندی قوی، گزینههای پایداری (persistence)، و ویژگیهای آماده پروداکشن، متمایز میشود.
در این مقاله، به موارد زیر خواهیم پرداخت:
🔹 راهاندازی Quartz.NET با ASP.NET Core و observability مناسب
🔹 پیادهسازی jobهای on-demand و تکرارشونده (recurring)
🔹 پیکربندی ذخیرهسازی پایدار با PostgreSQL
🔹 مدیریت دادههای job و نظارت بر اجرا
بیایید با راهاندازی اولیه شروع کنیم و به سمت یک پیکربندی آماده پروداکشن پیش برویم.
راهاندازی Quartz با ASP.NET Core 🔧
• ابتدا، Quartz را با ابزار دقیق (instrumentation) مناسب راهاندازی میکنیم.
باید چند پکیج NuGet را نصب کنیم:
Install-Package Quartz.Extensions.Hosting
Install-Package Quartz.Serialization.Json
# This might be in prerelease
Install-Package OpenTelemetry.Instrumentation.Quartz
• سپس، سرویسهای Quartz و ابزار دقیق OpenTelemetry را پیکربندی کرده و زمانبند را شروع میکنیم:
builder.Services.AddQuartz();
// Add Quartz.NET as a hosted service
builder.Services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true;
});
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
.AddQuartzInstrumentation();
})
.UseOtlpExporter();
تعریف و زمانبندی Jobها 📝
برای تعریف یک background job، باید اینترفیس IJob را پیادهسازی کنید. تمام پیادهسازیهای job به عنوان سرویسهای scoped اجرا میشوند، بنابراین میتوانید وابستگیها را در صورت نیاز تزریق کنید. Quartz به شما اجازه میدهد دادهها را با استفاده از دیکشنری JobDataMap به یک job پاس دهید. توصیه میشود فقط از انواع داده اولیه برای دادههای job استفاده کنید تا از مشکلات سریالسازی جلوگیری شود.
• هنگام اجرای job، چند راه برای واکشی دادههای job وجود دارد:
JobDataMap
یک دیکشنری از زوجهای کلید-مقدار
JobExecutionContext.JobDetail.JobDataMap
دادههای مخصوص job
JobExecutionContext.Trigger.JobDataMap
دادههای مخصوص trigger
MergedJobDataMap
دادههای job را با دادههای trigger ترکیب میکند
بهترین شیوه، استفاده از MergedJobDataMap برای بازیابی دادههای job است.
public class EmailReminderJob(
ILogger<EmailReminderJob> logger, IEmailService emailService) : IJob
{
public const string Name = nameof(EmailReminderJob);
public async Task Execute(IJobExecutionContext context)
{
// Best practice: Prefer using MergedJobDataMap
var data = context.MergedJobDataMap;
// Get job data - note that this isn't strongly typed
string? userId = data.GetString("userId");
string? message = data.GetString("message");
// ...
}
}
💡یک نکته: JobDataMap به صورت strongly-typed نیست. این محدودیتی است که باید با آن کنار بیاییم.
حالا، بیایید در مورد زمانبندی jobها صحبت کنیم.
زمانبندی یادآوریهای یکباره: 🗓
app.MapPost("/api/reminders/schedule", async (
ISchedulerFactory schedulerFactory,
ScheduleReminderRequest request) =>
{
var scheduler = await schedulerFactory.GetScheduler();
var jobData = new JobDataMap { /* ... */ };
var job = JobBuilder.Create<EmailReminderJob>()
.WithIdentity($"reminder-{Guid.NewGuid()}", "email-reminders")
.SetJobData(jobData)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"trigger-{Guid.NewGuid()}", "email-reminders")
.StartAt(request.ScheduleTime)
.Build();
await scheduler.ScheduleJob(job, trigger);
return Results.Ok();
});