One of the disadvantages of Scheduled APEX is that a scheduled class can’t be updated. Force.com creates an instance of the APEX class when it is scheduled, preventing it from being updated. You can’t edit a scheduled class, or update it via a ChangeSet, the Force.com IDE or a package update.
What’s more, Force.com prevents updates to any dependent classes as well. Thus it is quite easy for a scheduled class to “poison” an application – preventing many, if not all of its components, from being updated. As a result, updates to applications that use Scheduled Apex often require any scheduled jobs to be manually aborted before an update can take place.
It turns out, however, that a recent API update allows use of a design pattern that can help you avoid most of these problems.
Here’s how it works.
You still create a class that implements the Schedulable interface, but this class will be a simple wrapper that defines it’s own interface – call it IScheduleTest. This interface is identical to Schedulable. The execute method of the Schedulable global class creates an instance of a second class that implements the new IScheduleTest interface using the new Type class instantiation method. It then calls the execute method on that interface. It looks like this:
global class ScheduleTest Implements Schedulable { public Interface IScheduleTest { void execute(SchedulableContext sc); } global void execute(SchedulableContext sc) { Type targettype = Type.forName('CalledByScheduleTest'); if(targettype!=null) { IScheduleTest obj = (IScheduleTest)targettype.NewInstance(); obj.execute(sc); } } }
The second class looks something like this:
public class CalledByScheduleTest implements ScheduleTest.IScheduleTest { public void Execute(SchedulableContext sc) { System.debug('called in schedule'); } }
What does this accomplish?
You still can’t modify the ScheduleTest class once it’s scheduled, but this class is so simple, you may never need to update it. You can update the CalledByScheduleTest class. Using the Type.NewInstance method to create the class dynamically prevents the platform from seeing it as a dependent class.
I’ve been able to successfully update the CalledByScheduleTest class even during a managed package update as long as the scheduled ScheduleTest class remains unchanged. Though this design pattern is not officially documented (to my knowledge), I see no reason why it should not work reliably going forwards.
This design pattern eliminates one of the major impediments to using Scheduled Apex and is worth not only considering for new designs, but as a possible retrofit to existing applications.
The reason you can’t update the class is because an instance of the Schedulable class is stored away and deserialized on each execution. This allows you to store some state.
Your workaround eliminates this, because there is no instance of your interface being stored.
As it happens, it’s a pretty common use case NOT to require any state to be stored, so the obvious thing would be for SFDC to support a “stateless schedulable” interface of some kind, where the semantic would be that a new instance would be created on every execution.
It’s been on the backlog for a little while already, but your workaround gets you there now.
Thanks for the clarification. And even more – for confirmation that this is a legitimate workaround until the feature is added to the platform.
Thanks – very useful – I spend my life unscheduling classes so I can deploy code!
Can you suggest an example of a Scheduled Class when you would want to store State?
All I ever do is call other classes from my scheduled class (mostly batches) – but I am wondering if I am missing an opportunity to use Scheduler in other ways?
Hi Peter:
I’ve not yet come up with a situation where I wanted to store state in a scheduled class. I’m sure one could dream up such a scenario, but the price of storing state (the update problem) is far too high to justify it.
Hi, I know this is an old thread, but I have a legitimate (and possibly not uncommon) use case for serialized state in a scheduled class. I’ve got a class that has a local variable that causes it to do different processing (via population of SOQL). We need to run two different scheduled instances of this class on two different days each week. It’s convenient to be able to set one parameter, schedule for every Monday, then set the parameter to a different value to run every Wednesday.
In another case, I had a scheduled job that accepted fairly arbitrary parameters, which were then stored as state on a number of different schedules.
But I agree, the cost of this flexibility and the dreaded “scheduled class has jobs pending” error is WAY too high to justify it. Nice workaround here though.
These days, I never schedule Apex classes directly, I use a batch manager which is a single class that runs frequently and inspects a work table, which includes things like batchable class names, parameters, etc, and then uses reflection to instantiate them. This gets around both the “jobs pending” issue as well as the inexplicable governor limit on number of scheduled jobs (there is only ever 1 even though you can have 1000 pending jobs in the jobs table from 1000 different Apex classes).
I also follow the “batch manager” design pattern – it’s a good solution. Watch out for API 27 – you can get exceptions if you call System.Abort on a scheduled job (broken in API 25, fixed for API 26, broken for 27, and will be fixed again for 28). I’m also looking forward to trying out scheduled batch operations in Summer 13. I Wonder if they face this issue?
Excellent idea
Quick question about your comment though:
“I’ve been able to successfully update the CalledByScheduleTest class even during a managed package update as long as the scheduled ScheduleTest class remains unchanged.”
Can I assume that “ScheduleTest” class is part of the managed package as well or was it a class that was setup in the subscriber’s org?
Cheers!
It was part of the managed package.
Thanks for sharing the idea. A thought occurred to me when reading this for the umpteenth time-
Could a constructor with String param be added to ScheduleTest to use in the forName method? I’m thinking multiple scheduled instances that would kick off whichever implementing class is needed. Seems like this would give even less need to modify it later on.
Well, ScheduleTest is launched from the Apex scheduler, so I’m not sure what code would be setting the constructor parameter. How were you thinking of implementing this?
Remember also, the CalledByScheduleTest function can, in turn, delegate to other classes.
I was thinking it would be set when the object is created for use in a System.schedule call. Wouldn’t the state keep it for each instance?
I may be trying to head down the wrong path (haven’t finished the book yet 🙂 ). My dreamed up scenario: 1 class for the Apex scheduler, unlikely to change, that will launch any chosen process.
Well, you can do it – as you can pass a constructor when scheduling the class from within Apex. But I don’t think you can do that when scheduling the class through the UI, so you’d be putting in a limitation there. But the greater concern, I think, is that you would be committing to using a scheduled class that maintains state (the constructor parameter). It’s likely that Salesforce will create a version of scheduled Apex in the future that supports stateless classes and thus doesn’t prevent the class from being updated. Staying away from the constructor approach makes it easy to adopt the stateless class approach once it comes out – thus eliminating the problem completely.
Dan,
Thanks a lot for this information. I am working on my first scheduled job and this is definitely the way to go until SF addresses the update issue.
I created a schedAutoAssignFAs class which calls runAutoAssignFAs (which runs an apex batch class).
/*
ran as Anonymous APEX on developer console to setup the cron job to run every 30 minutes
schedAutoAssignFAs clsCRON = new schedAutoAssignFAs();
System.Schedule(‘FA Auto Assignment 00 Minute Mark’, ’0 00 * 1-31 1-12 ? *’, clsCRON);
System.Schedule(‘FA Auto Assignment 30 Minute Mark’, ’0 30 * 1-31 1-12 ? *’, clsCRON);
*/
Great post!
In our case the original scheduled jobs call the batch directly.
I tried to do the forName for the batch class that we instantiate as new like this
Type targettype=Type.forName(‘AccountCoverageSecurityBatchable’);
if(targettype!=null){
Database.Batchable obj = (Database.Batchable) targettype.NewInstance();
Database.executeBatch(obj, 1);
}
This didnt work, my guess is it lock all the Batchabe objects in this cass. So I need another interface on top. The problem is in our company that would be difficult due to integration and a lot of convincing to do.
Thanks
Well, using this approach for batch apex should “work” in the sense of the batch should start. But the article here referenced Scheduled Apex, which is a different beast. Or I may be misunderstanding your question, and what it is that “didn’t work”…..
I meant it still locks the updates on the Batchable classes. So I need an interface to decouple those, in order to update classes on deploy.
Thanks
I’m still a bit confused. A scheduled Apex class that is scheduled is locked. A batch class that is running or scheduled may be (I haven’t researched this).
All classes that are directly referenced by a scheduled Apex class will be locked (static reference, or a serialized variable). I can’t really judge what you’re looking at based on the code fragment.
Ok, no problem.
I was trying to see if I can get away with just dynamically loading Batchable class. Since we have many scheduled jobs that were done based on Apex documentation from Salesforce. After running a deploy test it still complained about BatchClass as running.
Changing from:
Dtabase.Batchable myBatchObject=new BatchClass(); implements Database.executeBatch(myBatchObject, 1);
To this:
Type targettype=Type.forName(‘BatchClass’);
if(targettype!=null){
Database.Batchable obj = (Database.Batchable) targettype.NewInstance();
Database.executeBatch(obj, 1);
}
The dynamic loading addresses a class locked because a serialized copy has been stored. I don’t think it would help you when it comes to a class that is actually being executed (as in a batch that is currently running).