No post de hoje, iremos criar uma simples aplicação utilizando o Quartz.NET para rodar processos em backgroud. Ainda vamos utilizar um Job Store compartilhado em um banco de dados PostgreSql, permitindo que os jobs possam rodar em mais de uma instância da aplicação.
Essa funcionalidade permite que caso alguma instância não possa executar o processo de backgroud (alguma indisponibilidade, por exemplo), esse job será atribuído a uma outra instância automaticamente. Garantido uma resiliência e escalabilidade as nossas soluções.
Antes do começar, recomendo uma leitura sobre alguns itens:
Vamos lá!
Inicialmente vamos criar um diretório raiz para nosso projeto.
mkdir QuartzJobScheduling
Dentro desse diretório, vamos iniciar um projeto em .NET do tipo worker.
dotnet new worker
Vamos adicionar alguns pacotes NuGet por meio do comando dotnet add package.
dotnet add package Quartz.Extensions.Hosting
dotnet add package Quartz.Serialization.Json
dotnet add package Npgsq
Devemos criar uma instância do PostgreSQL localmente para ser usado por nossa aplicação. Nesse post, eu criei esse banco de dados por meio de um docker compose dentro do meu raspberry pi (quem sabe isso não vale um post, ein?!), porém isso pode ser feito de várias outras formas.
Dentro do PostgreSQL recém criado, vamos criar um database chamado quartz.
Como mostrado na imagem acima, estou utilizando o Azure Data Studio.
Os schemas requisitados pelo Job Storage do Quartz podem sem encontrados no caminho abaixo:
https://github.com/quartznet/quartznet/blob/main/database/tables/tables_postgres.sql
Basta executar os scritps SQL no banco recém-criado, que teremos o seguinte resultado:
Feito isso, já podemos ir ao código da aplicação. Vamos inicialmente criar um método de extensão para configurar o Quartz:
- Habilitamos a injeção de dependências dentro dos jobs por meio do UseMicrosoftDependencyInjectionJobFactory;
- Configuramos o JobStorage com o método UsePersistentStore. Ele irá utilizar o banco de dados recém-criado como persistência;
- Configuramos um job (Classe MyJob.cs que iremos implementar), com a chave "MyJob" e que será executado a cada 5 segundos.
using Quartz;
using Quartz.Impl.AdoJobStore;
namespace QuartzJobScheduling.Jobs;
public static class JobConfigurations
{
public static IServiceCollection AddJobs(this IServiceCollection services, IConfiguration configuration)
{
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
q.SchedulerId = Guid.NewGuid().ToString();
q.SchedulerName = "Scheduler";
q.UsePersistentStore(opts =>
{
opts.UseClustering();
opts.UseJsonSerializer();
opts.UsePostgres(o =>
{
o.UseDriverDelegate<PostgreSQLDelegate>();
o.ConnectionString = configuration!.GetConnectionString("DefaultConnection");
o.TablePrefix = "qrtz_";
});
});
var jobKey = new JobKey("MyJob");
q.AddJob<MyJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("MyJob-trigger")
.WithCronSchedule("0/5 * * * * ?")); // run every 5 seconds
});
services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true;
});
return services;
}
}
Vamos agora criar o job declarado acima. Ele basicamente irá fazer o log da instância da aplicação que está executando o job e ainda informará quando que essa execução foi realizada.
using Quartz;
namespace QuartzJobScheduling.Jobs;
public class MyJob : IJob
{
private readonly ILogger<MyJob> _logger;
public MyJob(ILogger<MyJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation($"InstanceId:{context.FireInstanceId} - {DateTime.Now}");
return Task.CompletedTask;
}
}
Devemos colocar essas classes em um novo diretório chamado Jobs.
Vamos ainda adicionar a connections string do banco de dados PostgreSQL ao appsettings.json do nosso projeto.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=raspberrypi.local;Database=quartz;Port=5432;User Id=postgres;Password=<Your-Password>"
}
}
Para finalizar, devemos chamar o método de extensão criado anteriormente no Program.cs.
using QuartzJobScheduling.Jobs;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((builder, services)=>
{
services.AddJobs(builder.Configuration);
})
.Build();
host.Run();
Todo o desenvolvimento está feito. É hora de fazermos os testes.
Para isso, vamos simular duas instâncias da aplicação rodando simultaneamente. Devemos fazer o publish da aplicação duas vezes:
dotnet publish -o publishOne
dotnet publish -o publishTwo
Vamos abrir dois terminais, e em cada terminal, rodar o seguinte comando dentro de cada diretório publicado anteriormente:
dotnet QuartzJobScheduling.dll
Podemos observar que as execuções (a cada 5 segundos) estão sendo distribuídas entre as instâncias ativas do nosso aplicativo.
Tudo ocorreu conforme o planejado!
Você já pode baixar o projeto por esse link, e não esquece de me seguir no LinkedIn!
Até a próxima, abraços!