Automatisation des tâches avec Quartz.NET

Quartz permet une planification flexible des tâches dans les applications .NET, ce qui le rend idéal pour les systèmes nécessitant une automatisation, comme les notifications, la génération de rapports et la synchronisation périodique. Dans cet article, nous allons créer une tâche de nettoyage périodique de l’historique avec Quartz.
Exécuter des routines répétitives n’est qu’une des nombreuses tâches d’un développeur back-end et, pour être honnête, écrire un while(true)
boucle avec Thread.Sleep n’est pas exactement la solution la plus élégante et certainement pas la plus évolutive. C’est là que Quartz.NET apparaît comme une excellente option pour planifier des tâches pour les applications .NET.
Dans cet article, nous apprendrons comment automatiser les tâches récurrentes à l’aide de Quartz.NET en implémentant une tâche pour effacer l’historique des processus. De plus, nous verrons comment configurer une planification pour l’exécution automatisée du service de travail.
⏰ Qu’est-ce que Quartz.NET ?
Quartz.NET est un système de planification de tâches open source (sous licence Apache, version 2.0) conçu pour les applications .NET qui doivent exécuter des tâches automatisées avec précision et fiabilité.
Il est conçu pour gérer des scénarios de planification complexes, tels que les exécutions basées sur cron, les intervalles personnalisés, les dépendances de tâches et l’exécution distribuée, ce qui en fait un excellent choix pour les systèmes d’entreprise et les applications back-end.
🤔Pourquoi choisir le quartz ?
Quartz.NET se démarque en matière de planification de tâches dans les applications .NET en raison de sa simplicité et des fonctionnalités disponibles. Il vous permet de tout créer, des tâches simples à intervalles fixes aux planifications complexes avec des expressions cron, prenant en charge les exécutions récurrentes, les retards, les dépendances de tâches et la persistance de la base de données.
De plus, il est open source (Apache 2.0), dispose d’une bonne documentation, d’une large adoption par la communauté et d’une intégration facile avec l’injection de dépendances et les applications ASP.NET Core.
Bien qu’il existe des solutions alternatives, Quartz.NET s’impose comme un excellent choix pour les automatisations telles que la livraison d’e-mails, la génération de rapports, la synchronisation des données et d’autres processus critiques qui exigent fiabilité et contrôle de l’exécution.
🫧 Création d’un travail de nettoyage d’historique avec Quartz
Comme cas d’utilisation, nous allons créer une tâche pour nettoyer l’historique du processus. L’application aura les fonctionnalités suivantes :
- Un travail appelé
TaskMonitor
qui recherche les processus survenus avant les 30 derniers jours et les supprime ; il insère ensuite un enregistrement dans une table de notification - Intégration avec SQL Server et EF Core
- Exemples de données pour vérifier le fonctionnement du travail
- Configurer l’intervalle d’exécution avec une expression cron
Le code source complet de l’application créée dans l’article est disponible dans ce référentiel GitHub : Code source de TaskMonitor.
🪄 Création de l’application
Pour créer l’exemple d’application, vous pouvez utiliser la commande suivante :
dotnet new web -n TaskMonitor
Installation des packages
Ensuite, installez les packages requis pour créer le travail.
Forfaits Quartz :
dotnet add package Quartz
dotnet add package Quartz.AspNetCore
Autres forfaits :
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
Classes modèles
Pour créer les classes de modèles, créez d’abord un nouveau dossier appelé « Modèles » et, à l’intérieur, ajoutez les classes suivantes :
namespace TaskMonitor.Models;
public class ProcessHistory
{
public int Id { get; set; }
public string ProcessName { get; set; }
public DateTime CreatedAt { get; set; }
public string Status { get; set; }
public string Details { get; set; }
public string CreatedBy { get; set; }
}
namespace TaskMonitor.Models;
public class Notification
{
public int Id { get; set; }
public string Message { get; set; }
public DateTime CreatedAt { get; set; }
}
Création du contexte et des classes de départ
L’étape suivante consiste à créer la classe de contexte pour configurer les entités EF Core et une classe pour initialiser les exemples de données. Alors, créez un nouveau dossier appelé « Data » et ajoutez-y les classes suivantes :
using Microsoft.EntityFrameworkCore;
using TaskMonitor.Models;
namespace TaskMonitor.Data;
public class AppDbContext : DbContext
{
public DbSet<ProcessHistory> ProcessHistory { get; set; }
public DbSet<Notification> Notifications { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}
using Microsoft.EntityFrameworkCore;
using TaskMonitor.Data;
using TaskMonitor.Models;
namespace FinanceTracker.Data;
public static class DbInitializer
{
public static async Task SeedAsync(AppDbContext dbContext)
{
await dbContext.Database.MigrateAsync();
if (!await dbContext.ProcessHistory.AnyAsync())
{
var now = DateTime.UtcNow;
var sampleEntries = Enumerable.Range(1, 100)
.Select(i => new ProcessHistory
{
CreatedAt = now.AddDays(-i),
ProcessName = $"Sample Process: #{i}",
Status = "Completed",
Details = $"Process details: #{i}",
CreatedBy = "admin"
})
.ToList();
await dbContext.ProcessHistory.AddRangeAsync(sampleEntries);
await dbContext.SaveChangesAsync();
}
}
}
Création de la catégorie d’emplois
Créons maintenant la classe de travail qui effectuera le nettoyage des données et insérons le journal de notification.
Créez un nouveau dossier appelé « Jobs » et ajoutez-y la classe suivante :
using Microsoft.EntityFrameworkCore;
using Quartz;
using TaskMonitor.Data;
using TaskMonitor.Models;
namespace TaskMonitor.Jobs;
public class CleanupHistoryJob : IJob
{
private readonly AppDbContext _dbContext;
private readonly ILogger<CleanupHistoryJob> _logger;
public CleanupHistoryJob(AppDbContext dbContext, ILogger<CleanupHistoryJob> logger)
{
_dbContext = dbContext;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
var cutoffDate = DateTime.UtcNow.AddDays(-30);
var oldEntries = await _dbContext.ProcessHistory
.Where(h => h.CreatedAt < cutoffDate)
.ToListAsync();
if (oldEntries.Count == 0)
{
_logger.LogInformation("No old history entries found for cleanup.");
return;
}
await RemoveHistory(oldEntries);
var notification = new Notification
{
Message = $"{oldEntries.Count} history entries cleaned up at {DateTime.UtcNow:O}",
CreatedAt = DateTime.UtcNow
};
await SaveNotification(notification);
}
private async Task SaveNotification(Notification notification)
{
_dbContext.Notifications.Add(notification);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Cleanup notification created.");
}
private async Task RemoveHistory(List<ProcessHistory> oldEntries)
{
_dbContext.ProcessHistory.RemoveRange(oldEntries);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Deleted {Count} old history entries.", oldEntries.Count);
}
}
Notez que le CleanupHistoryJob
la classe implémente Quartz IJob
interface, ce qui signifie que chaque fois que le travail est exécuté, le Execute(IJobExecutionContext context)
La méthode sera automatiquement appelée par le planificateur, contenant la logique principale de nettoyage de l’historique.
Configuration de la classe de programme
Ajoutons maintenant les paramètres de tâche que nous avons créés précédemment à la classe Program. Ajoutez-y le code suivant :
using Microsoft.EntityFrameworkCore;
using TaskMonitor.Data;
using Quartz;
using TaskMonitor.Jobs;
using FinanceTracker.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddQuartz(q =>
{
var jobKey = new JobKey("CleanupHistoryJob");
q.AddJob<CleanupHistoryJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("CleanupHistoryTrigger")
);
});
builder.Services.AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await DbInitializer.SeedAsync(dbContext);
}
app.Run();
A noter que l’exécution du job se fait via le code :
builder.Services.AddQuartz(q =>
{
var jobKey = new JobKey("CleanupHistoryJob");
q.AddJob<CleanupHistoryJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("CleanupHistoryTrigger") );
});
builder.Services.AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
Ici, nous configurons Quartz.NET pour exécuter le CleanupHistoryJob
emploi. Tout d’abord, le service Quartz est enregistré en utilisant builder.Services.AddQuartz(...)
en passant une configuration interne via le q => { ... }
fonction.
Au sein de cette configuration, nous créons une clé d’identification (jobKey
) pour le travail utilisant new JobKey("CleanupHistoryJob")
. Cette clé sert à identifier de manière unique le travail dans le planificateur.
Ensuite, le CleanupHistoryJob
le travail est ajouté à l’aide q.AddJob<CleanupHistoryJob>(...)
en l’associant à la clé créée.
Après cela, un déclencheur est configuré pour cette tâche à l’aide de q.AddTrigger(...)
. Ce déclencheur détermine quand le travail doit être exécuté. Dans l’exemple, il est lié au travail à l’aide de .ForJob(jobKey)
et reçoit sa propre identité appelée CleanupHistoryTrigger
.
Enfin, builder.Services.AddQuartzHostedService(...)
enregistre le service hébergé qui permet à Quartz de fonctionner pendant que l’application est active. Le opt.WaitForJobsToComplete = true
L’option le définit de sorte que, lorsque l’application se ferme, Quartz attend la fin de toutes les tâches en cours avant de s’arrêter.
🚀 Exécuter le travail
Maintenant que tout est configuré, nous pouvons exécuter le travail et vérifier son exécution grâce aux données. Lorsque le travail s’exécute, la base de données sera chargée et le travail supprimera les enregistrements créés il y a jusqu’à 30 jours de la table d’historique et enfin insérera un enregistrement dans la table de notification, comme le montrent les images ci-dessous :
Exécution du travail montrant que 71 enregistrements ont été supprimés.
Après avoir terminé l’exécution du travail, lors de la vérification des données, il est possible de constater que la suppression des 71 enregistrements et l’insertion dans l’historique ont été exécutées :
De cette façon, nous disposons d’un travail fonctionnel qui exécutera la routine planifiée.
⌛ Comprendre le concept Cron
En technologie, « cron » est un concept communément associé aux routines, provenant de l’utilitaire cron Unix/Linux utilisé pour planifier l’exécution automatique de commandes ou de scripts à des moments et à des intervalles spécifiques. Le nom est inspiré de « chronos » (du grec « temps »).
Dans le contexte spécifique des tâches backend, une expression cron est une chaîne qui indique quand la tâche doit être exécutée, en utilisant un format standardisé avec des champs séparés par des espaces, tels que :
Chaque champ peut contenir des nombres, des plages, des virgules et des caractères génériques (* pour n’importe quelle valeur) :
- Minutes : 0-59
- Heure : 0-23
- Jour du mois : 1-31
- Mois : 1-12 ou noms abrégés (JAN, FEB…)
- Jour de la semaine : 0-6 (dimanche=0) ou noms (SUN, MON…)
Dans le travail que nous avons construit ci-dessus, pour exécuter le travail localement, nous n’avons effectué aucune configuration de temps supplémentaire. Il est cependant possible de configurer un intervalle de temps d’exécution, avec le .WithCronSchedule("CRON") );
méthode d’extension dans la configuration Quartz dans la classe Program.
La configuration complète ressemblerait à ceci :
builder.Services.AddQuartz(q =>
{
var jobKey = new JobKey("CleanupHistoryJob");
q.AddJob<CleanupHistoryJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("CleanupHistoryTrigger")
.WithCronSchedule("0 3 * * *"));
});
De cette façon, le travail s’exécutera tous les jours à 3h00 du matin
🌱Conclusion
Gérer les routines quotidiennes telles que nettoyer les anciennes données, envoyer des e-mails et générer des rapports fait partie de la routine quotidienne de la plupart des développeurs back-end. Dans ce contexte, trouver des alternatives qui simplifient ce travail est toujours un objectif, et c’est dans ces situations que Quartz brille. Sa configuration simple et son intégration facile avec .NET sont ses principaux atouts.
Dans cet article, nous avons vu comment implémenter une tâche de nettoyage de l’historique intégrée à SQL Server et appris, de manière pratique, comment configurer un cron pour exécuter la tâche à des intervalles définis. Cependant, il existe encore des fonctions dans Quartz qui peuvent être explorées, telles que l’enregistrement de l’historique d’exécution des tâches à l’aide de ressources natives telles que Persistent Store, où Quartz enregistre le script d’exécution dans ses propres tables. Ceci est particulièrement utile si vous avez besoin de télémétrie de planification.
J’espère que cet article vous a aidé à mieux comprendre comment utiliser Quartz dans vos projets et qu’il servira de point de départ pour automatiser davantage vos routines backend.
Source link