1

I have a batch class which performs certain DML operation in execute method based on certain conditions. Also performing an update under finish method. I have a constructor where parameters of type string and date are passed.

We are scheduling this batch class for one requirement A and it is working fine. Now we have a new requirement B and its similar to requirement A, so we are using the same batch class but passing different parameters to the constructor based on the requirement. Now we need to send email to admin/group whenever this batch job fails for both requirements. So we used Database.RaisesPlatformEvents interface which fires BatchApexErrorEvent when batch class fails. We used a platform event triggered flow to subscribe BatchApexErrorEvent and then fire an email from flow.

Problem Statement: The problem here is since we are using same batch class for both requirements A and B we are unable to find or differentiate for which requirement the batch class has failed. Is there any solution to find for which parameter the batch class has failed so that we can differentiate and send failure emails accordingly.

Example of the Batch class:

public class SearchAndReplace implements Database.Batchable<sObject>{
 private String Query;
   private Date Entity; 

public SearchAndReplace(String q, Date e){

      Query=q; Entity=e;
   }

   public Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }

public void execute(Database.BatchableContext BC, List<sObject> scope){
     for(sobject s : scope){
     //conditions and operations
     }
  //performing DML operations
    }

   public void finish(Database.BatchableContext BC){
//update statement
   }
}

Using the batch class for Requirement A: Date dateInstance = date.newInstance(2022,07,28); SearchAndReplace batchInstance = null; batchInstance = new batchInstance ('Account',dateInstance ); Id jobid = Database.executeBatch(batchInstance,200);

Using the batch class for Requirement B: Date dateInstance = date.newInstance(2022,07,28); SearchAndReplace batchInstance = null; batchInstance = new batchInstance ('Contact',dateInstance ); Id jobid = Database.executeBatch(batchInstance,200);

4
  • What have you tried? What part of it isn't working? Please edit your question to narrow down exactly what part of this you're stuck on.
    – Nick Cook
    Jul 28 at 5:16
  • Hi @NickCook We are trying to send email notification when batch apex class fails. We tried a platform event triggered flow to send email notification when batch class fails. We are able to receive email when batch class fails for some reason, but the problem we are facing here is we are using the same batch class for both requirements, so we are not able to differentiate for which requirement the batch class is failing. Requirement A: batchInstance = new batchInstance ('Account',dateInstance ); Requirement B: batchInstance = new batchInstance ('Contact',dateInstance );
    – Sudharsan
    Jul 28 at 5:41
  • If your batch is distinguished by the SObject type that it processes, you could simply look at the platform event's JobScope and determine the SObject type by examining the first ID: there are means to take an Id and get the SObjectType from it.
    – Phil W
    Jul 28 at 6:34
  • Hi @PhilW Thanks for your response. Unfortunately batch is not distinguished by the SObject type. It is distinguished by one of the parameters passed and that parameter is stored as a value in one of the fields of the object.
    – Sudharsan
    Jul 28 at 7:12

1 Answer 1

0

If you continue using the BatchApexErrorEvent to track errors (I understand that catching exceptions locally in the batch isn't always possible, especially if the batch is exceeding limits) then you have only a couple of options because these events do not allow you to add any custom fields that you could use to distinguish the batches.

The first is to consider whether any of the IDs held in the JobScope could help you reverse-engineer which batch instance failed. If that's not possible then you need to use the event's AsyncApexJobId to allow you to correlate the execution.

I believe the following will give you what you need, at some cost:

  1. Define a custom Object in the Schema, let's call it SearchAndReplaceParams__c, and add custom Fields for each relevant parameter you need to distinguish the batches.
  2. Include an ApexJob__c field in this custom object, type Text and length 18.
  3. In the batch start method, using the BatchableContext, do something like:
public Database.QueryLocator start(Database.BatchableContext context) {
    // First capture the parameters
    insert new SearchAndReplaceParams__c(ApexJob__c = context.getJobId(),
                                         /* the fields ... */);

    // Now do what you normally do here...
}
  1. In the platform event trigger you can map back to the parameters, thus:
List<Id> apexJobIds = ...; // Collect these from the platform events you received
SearchAndReplaceParams__c jobParams = [SELECT ApexJob__c, ... FROM SearchAndReplaceParams__c WHERE ApexJob__c IN :apexJobIds];
Map<String, SearchAndReplaceParams__c> paramsByApexJobId = new Map<String, SearchAndReplaceParams__c>();

for (SearchAndReplaceParams__c params : jobParams) {
    paramsByApexJobId.put(params.ApexJob__c, params);
}

// Now iterate over the platform events and get the params out of paramsByApexJobId
// using paramsByApexJobId.get(event.AsyncApexJobId). Now you can map the batch
// error to the set of parameters and do your specific processing
...

// Finally, find the SearchAndReplaceParams__c for batch jobs that are no longer
// queued or running and delete them (to ensure you don't bloat the database)
Map<Id, AsyncApexJob> jobsToClean = new Map<Id, AsyncApexJob>([
    SELECT Id
        FROM AsyncApexJob
        WHERE Id IN :apexJobIds
            OR (JobType = 'BatchApex'
                AND Status NOT IN ('Holding', 'Queued', 'Preparing', 'Processing'))
        LIMIT 100]);

List<SearchAndReplaceParams__c> paramsToDelete = [SELECT Id FROM SearchAndReplaceParams__c WHERE ApexJob__c IN :jobsToClean.keySet()];

delete paramsToDelete;

By the way, I have created an idea that would make this much easier. Feel free to upvote it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.