Introduction
Azure Service Fabric Services can be configured to exhibit trigger based behavior similar to WebJobs and Azure Functions.
Time triggered / Scheduler Service in Azure Service Fabric
To create a time triggered (scheduler) Service in Azure Service Fabric, I can think of following two quick options among the possible ways.
- Create a time triggered WebJob and add/deploy it as a guest executable in Azure Service Fabric.
- Create Azure Service Fabric Stateless Service and implement the listener to handle jobs.
Here I'm taking the second approach which involves the respective listener to be created. Rather than writing new Scheduling framework, I'm using the Quartz.Net framework. I'm using the CRON expression behavior to have the behavior inline with the time trigger behavior of WebJobs and Azure Functions.
I'll be explaining two approaches below.
- Simple Time trigger Service
- Time trigger Service using Dependency Injection
Simple Time trigger Service
- Uses Quartz.Net framework.
- Without Dependency Injection.
- Listerner gets all the
IJob
types from the current assembly withJobInfoAttribute
decorated. JobInfoAttribute
holds the name and CRON expression of the job.- Code samples can be found in my github repo here.
Snippet 1 (SimpleTimeTriggerListener)
using Microsoft.ServiceFabric.Services.Communication.Runtime; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Reflection; using Quartz; using TimeTriggerService; using Quartz.Impl; namespace SimpleTimeTriggerService { public class SimpleTimeTriggerListener : ICommunicationListener { private IScheduler scheduler; public void Abort() { scheduler.Shutdown(false); } public Task CloseAsync(CancellationToken cancellationToken) { scheduler.Shutdown(true); return Task.FromResult(true); } public Task<string> OpenAsync(CancellationToken cancellationToken) { var tasks = Assembly.GetExecutingAssembly() .GetTypes() .Where( t => typeof(IJob).IsAssignableFrom(t) && Attribute.IsDefined(t, typeof(JobInfoAttribute))); foreach (var task in tasks) { var schedulerFactory = new StdSchedulerFactory(); scheduler = schedulerFactory.GetScheduler(); var jobInfo = Attribute.GetCustomAttribute(task, typeof(JobInfoAttribute)) as JobInfoAttribute; var jobName = jobInfo == null ? task.Name : jobInfo.Name; var job = new JobDetailImpl(jobName, null, task); var trigger = TriggerBuilder.Create() .WithIdentity($"{jobName}Trigger", null) .WithCronSchedule(jobInfo.CronExpression) .ForJob(job) .Build(); scheduler.ScheduleJob(job, trigger); } scheduler.Start(); return Task.FromResult("Sample Simple Job scheduler"); } } }
Time trigger Service using Dependency Injection
- Uses Quartz.Net framework.
- With Dependency Injection using Autofac (Autofac integration package for Quartz.Net).
- Listerner gets all the
IJob
types from the current assembly withJobInfoAttribute
decorated.
JobInfoAttribute
holds the name and CRON expression of the job.
- Code samples can be found in my github repo here.
Snippet 2 (TimeTriggerListener with Autofac)
using Microsoft.ServiceFabric.Services.Communication.Runtime; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Threading; using Autofac; using Quartz; using Autofac.Extras.Quartz; using System.Reflection; namespace TimeTriggerService { internal class TimeTriggerListener : ICommunicationListener { private IScheduler scheduler; public void Abort() { scheduler.Shutdown(false); } public Task CloseAsync(CancellationToken cancellationToken) { scheduler.Shutdown(true); return Task.FromResult(true); } public Task<string> OpenAsync(CancellationToken cancellationToken) { ContainerBuilder builder = new ContainerBuilder(); builder.RegisterModule(new QuartzAutofacFactoryModule()); builder.RegisterModule(new QuartzAutofacJobsModule(Assembly.GetExecutingAssembly())); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(x => typeof(IJob).IsAssignableFrom(x)).As<IJob>(); builder.RegisterType<Logger>().As<ILogger>(); builder.RegisterType<JobScheduler>().AsSelf(); IContainer container = builder.Build(); ConfigureScheduler(container); return Task.FromResult("Sample Job scheduler"); } private void ConfigureScheduler(IContainer container) { IEnumerable<IJob> jobList = container.Resolve<IEnumerable<IJob>>(); var jobScheduler = container.Resolve<JobScheduler>(); this.scheduler = jobScheduler.Start(); } } }
Snippet 3 (JobScheduler)
using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Generic;
namespace TimeTriggerService
{
public class JobScheduler
{
private IScheduler scheduler;
private IEnumerable<IJob> jobs;
public JobScheduler(IScheduler schedulerParam, IEnumerable<IJob> jobsParam)
{
this.scheduler = schedulerParam;
this.jobs = jobsParam;
}
public IScheduler Start()
{
foreach (var job in jobs)
{
var task = job.GetType();
var jobInfo = Attribute.GetCustomAttribute(task, typeof(JobInfoAttribute)) as JobInfoAttribute;
var jobName = jobInfo == null ? task.Name : jobInfo.Name;
var jobDetail = new JobDetailImpl(jobName, null, task);
var trigger =
TriggerBuilder.Create()
.WithIdentity($"{jobName}Trigger", null)
.WithCronSchedule(jobInfo.CronExpression)
.ForJob(jobDetail)
.Build();
scheduler.ScheduleJob(jobDetail, trigger);
}
scheduler.Start();
return scheduler;
}
}
}
Full source code has been provided here.
I guess this was not intended usage of ICommunicationListener. Why did you not put this logic directly to the service?
ReplyDelete