Quartz.NET meets Design Patterns
March 23, 2015 3 Comments
This is the third in a series of posts.
In the last post I showed you how I set up some tests for my implementation of retries with Quartz.NET. I repeatedly hinted at some neat tricks to make things more convenient so here they are.
Quartz.NET requires that your jobs only throw JobExecutionExceptions (as explained at the very bottom of this page). There are reasons why this restriction makes sense but I don’t want to litter my business logic with repetitions of the exact same exception handling code. I think that’s what DRY is all about. I don’t want to force all of my jobs to inherit from a specific base class either. At least not for the single purpose of following Quartz.NET’s rules for exception handling.
But by applying a decorator to my job classes I can fix that once and for all.
public class EnsureJobExecutionExceptionDecorator : IJob { private readonly IJob inner; public EnsureJobExecutionExceptionDecorator(IJob inner) { this.inner = inner; } public void Execute(IJobExecutionContext context) { try { this.inner.Execute(context); } catch (JobExecutionException) { throw; } catch (Exception cause) { throw new JobExecutionException(cause); } } }
JobExecutionExceptions
are simply rethrown. Which allows you to throw them in your job if you have to tweak what to tell the scheduler. All other exceptions become InnerExceptions of a new JobExecutionException
. Done. Now that was easy.
But how do I ensure that each time Quartz.NET instantiates a job the decorator is in place?
By replacing the scheduler’s default IJobFactory with something more advanced. For my playground I derived from the PropertySettingJobFactory base class and use Unity to create my jobs.
private sealed class UnityJobFactory : PropertySettingJobFactory { private readonly IUnityContainer container; public UnityJobFactory(IUnityContainer container) { this.ThrowIfPropertyNotFound = false; this.WarnIfPropertyNotFound = true; this.container = container; } public override IJob NewJob( TriggerFiredBundle bundle, IScheduler scheduler) { Type jobType = bundle.JobDetail.JobType; IJob job = (IJob)this.container.Resolve(jobType); JobDataMap data = new JobDataMap(); data.PutAll(scheduler.Context); data.PutAll(bundle.JobDetail.JobDataMap); data.PutAll(bundle.Trigger.JobDataMap); this.SetObjectProperties((object)job, data); return job; } public override void ReturnJob(IJob job) { this.container.Teardown(job); } }
And then its a simple matter to configure Unity to wrap every job it creates with the EnsureJobExecutionExceptionDecorator
. Not that hard is it?
And finally there is the code snippet that unfreezes my test thread when I’m done.
public class UnfreezeWhenJobShouldNotRunAgain : IRetryStrategy { private readonly IRetryStrategy inner; private readonly ManualResetEvent reset; public UnfreezeWhenJobShouldNotRunAgain( IRetryStrategy inner, ManualResetEvent reset) { this.inner = inner; this.reset = reset; } public bool ShouldRetry(IJobExecutionContext context) { bool shouldRetry = this.inner.ShouldRetry(context); if (!shouldRetry) { this.reset.Set(); } return shouldRetry; } public ITrigger GetTrigger(IJobExecutionContext context) { return this.inner.GetTrigger(context); } }
Yet another decorator. Whenever the RetryJobListener
from the first post of this series queries the IRetryStrategy
wether a job should be run again the decorator checks for “yes” or “no”. And in case of a “no” it will set the ManualResetEvent and allow the test thread to continue.
So we have decorators, an abstract factory and dependency injection here. And all of that in less than 200 lines of code. All pieces short and to the point but by combining them you can build mighty powerful solutions that are still clean and easy to understand.
I hope you enjoyed the series and come back for another read. See you soon!
Pingback: Quartz and other gems | Outlawtrail - .NET Development
Hi
Do U can publish url to github and add it in your post?
Hi Anton,
sorry I don’t use GitHub and don’t have plans to do so in the near future. But you can find the code on my playground on codeplex. Follow this link.
HIH