Design: Run Apex Batch Job Dynamically at desired intervals in Salesforce

 Design: Run Apex Batch Job Dynamically at desired intervals in Salesforce

In this post, we will try to Design a process using Custom Metadata settings to run the Batch job at desired intervals.

Custom Metadata Type – Schedule Batch Apex Jobs

This Custom Metadata Type is used to define how often the batch jobs must be executed. This custom metadata type can be used in the future for other batch jobs that will be newly developed in the course of the Salesforce Application Development.

CustomSettings

As seen in the illustration above, the custom metadata type comprises of 3 custom fields namely:

  1. Every X Minutes (API Name: Every_X_Minutes__c): This is a text field. If defined will ensure the batch job is executed every “X” Usually the value for “X” should be multiples of 5 and less than 60. For Eg: 20, 30, 40, 45.

 

  1. Execute Every Hour (API Name: Execute_Every_Hour__c): This is a checkbox field. If checked, will ensure the batch job is executed every hour in a day.

 

  1. Hour Of The Day (API Name: Hour_Of_The_Day__c): This is a text field. If defined will ensure the batch job is executed at the Hour of the day mentioned in this field. The hour should be mentioned in the 24-hour format and the values can range between 0 and 23. For Eg: If the batch must be scheduled to run at 1 PM by the scheduler job, then the hour to be mentioned in this field must be 13. Likewise, if it must be scheduled to run at 4 PM then the hour to be specified in this field must be 16.

Validation Rules on the Custom Metadata Type

Validationrule

The above validation rules can be defined on the Custom Metadata Type in order to ensure that multiple inputs aren’t provided when it comes to the batch scheduling time intervals. The intention for this solution to work is to ensure either the batch is configured to be run every “X” minutes, or every hour or at a specific time in the day. In Salesforce, the CRON Expression for scheduling the same would change every for each parameter that is being set depending on whether the batch has to be executed every “X” minutes or every hour in the day or at a specific time in the day.

Like for E.g.: Batch Job could be executed every hour in a day, or the Batch Apex Job could be executed once everyday at some specific time of the day. However, it is very unlikely that batches can be executed every 5 mins or every 10 mins. There may be possibility that batches would need to be executed ad-hoc typically in ISV product development scenarios. Even in that case it may so be the case that a Lightning or Salesforce Classic based button placed on a Lightning Page or Visualforce Page would be invoking the batch job ad-hoc.

Moreover, the downside or overhead of scheduling batch-jobs to run every 5 mins will result in the batch apex job running at different times as it moreover executes in an asynchronous manner. This can result in wrong outputs or results especially if we are calculating totals as aggregate or for that matter provisioning or revoking permissions in the form of permission-sets or share records.

 Setting CRON expression to run batch every ‘X’ Minutes.

As a developer, the idea is to ensure the Scheduler gets the right CRON job expressions each time it must invoke the batch apex job at the regular interval as expected. Always remember that, to execute the batch job every 30 mins the batch job would need to be invoked twice every hour, which technically implies that the scheduler will have to be invoked twice every hour.

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 0 * * * ? ‘, new SchedulerJob());

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 30 * * * ? ‘, new SchedulerJob());

With the above invocation, we will observe that the batch job gets scheduled for every 30 mins that is at the 60th minute or 0th minute of every hour and at the 30th minute in an hour. The Invoker class must call the Scheduler class twice so that the batch job gets called from the Scheduler Job.

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 0 * * * ? ‘, new SchedulerJob());

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 15 * * * ? ‘, new SchedulerJob());

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 30 * * * ? ‘, new SchedulerJob());

System.schedule(‘SchedulerJob – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), ‘0 45 * * * ? ‘, new SchedulerJob());

In the above e.g. if let us say we set the custom metadata type in such a manner that we would like to run the batch job through the Scheduler Job, then in such a case the Scheduler will need to be invoked 5 times i.e. at 0th minute in the hour, 15th minute in the hour, 30th minute in the hour and at the 45th minute in the hour.

public class InvokeScheduler {

   

    public void SchedulerMethod() {

        Schedule_Batch_Apex_Jobs__mdt scheduleBatchMDT = [SELECT Every_X_Minutes__c, Execute_Every_Hour__c, Hour_Of_The_Day__c FROM Schedule_Batch_Apex_Jobs__mdt WHERE DeveloperName = ‘Unique Name of the Custom Metadata Type data record‘];

        String CRON_EXPR;

       

        if((scheduleBatchMDT.Every_X_Minutes__c != null) || Test.isRunningTest()){                             

           if(Test.isRunningTest()){

            scheduleBatchMDT.Every_X_Minutes__c = ’20’;

           }

          

            list<String> CRON_ SubStringList = new list<String>();

            Integer X_MINS = Integer.valueOf(scheduleBatchMDT.Every_X_Minutes__c);                                                                   

            Integer Interval = Integer.valueOf(60/X_MINS);

            system.debug(‘*** Interval ===> ‘+Interval);

            if(Interval == 1){

                Interval += 1;

            }

            Integer seriesVal = X_MINS;

            Integer seriesCount = 0;

            for(Integer i = 0; i < Interval; i++){ 

                seriesCount += seriesVal;

                if(seriesCount < 60){

                    CRON_SubStringList.add(String.valueOf(seriesCount));

                }

            }

                                  

            for(String schedulerMinsInterval : CRON_ SubStringList){

                CRON_EXPR = ‘0 ‘+schedulerMinsInterval+’ * * * ?’;               

                scheduleBatch.scheduleMe(CRON_EXPR);

            }

        }

       

        if((scheduleBatchMDT.Execute_Every_Hour__c != false) || Test.isRunningTest()){                       

            CRON_EXPR = ‘0 0 * * * ?’;

            system.debug(‘*** I am going to execute the batch every hour ****’);

            scheduleBatch.scheduleMe(CRON_EXPR);

        }

       

        if((scheduleBatchMDT.Hour_Of_The_Day__c != null) || Test.isRunningTest()){                        

            if(Test.isRunningTest()){

                scheduleBatchMDT.Hour_Of_The_Day__c = ’21’;

            }

           

            CRON_EXPR = ‘0 0 ‘+scheduleBatchMDT.Hour_Of_The_Day__c+’ * * ?’;

            scheduleBatch.scheduleMe(CRON_EXPR);

        }                              

    }

}

 

The above class is the invoker class that ensures that several instances of the scheduler are instantiated to run at the specific time in regular intervals. This class is responsible for preparing the CRON expression that will define how often the Scheduler Jobs must be instantiated, which in turn will be responsible for invoking the batch. Before calculating the CRON expression it is important to understand what setup as part of the metadata-type has been configurational settings.

The Interval variable is being used to identify the iterations we need to go through to build on the CRON Expression. CRON_ SubStringList must be a list collection to determine the series in minutes how often the scheduler must be scheduled because the list collection helps us get the string in an ordered manner vs. a set collection because the set is an unordered collection.

 

Can we call the Scheduler class from the Scheduler class itself (Recursion calls)?

We Cannot do this because the Scheduler goes into an infinite loop of a never-ending state and keeps spawning new processes.  Some blogs also suggest to hit the CRONTrigger repository table to find the immediate last scheduler’s Job ID and to abort it. This will not be the actual solution to this problem. This is the very reason it makes sense to control the Scheduler job invocation from an external class, which in this case will be the InvokeScheduler Apex Class.

 

global class scheduleBatch implements Schedulable {             

   

    global static void scheduleMe(String CRON_EXPR) {

        System.schedule(‘ Schedule Job – ‘+String.valueOf(Math.abs(Crypto.getRandomLong())).substring(0, 5), CRON_EXPR, new scheduleBatch());

    }

 

    global void execute(SchedulableContext sc) {

       Database.executeBatch(new actualBatchJobName(), 200);    

    }    

}

 

The above Apex class is none other than the actual Scheduler Class that is implementing the Schedulable interface. This class has two methods one is the execute( ) method that runs in the Schedulable Context and the other i.e. scheduleMe() is the static method which is invoked from the InvokeScheduler class, to ensure the scheduler is instantiated dynamically depending upon the no of instance that has to be spawned based on the Custom Metadata Type setup.

 

Post Contributor:

Rakesh Ramaswamy

https://www.linkedin.com/in/rakesh-ramaswamy-38062385/

sfdclightning

Related post