Responsive Advertisement

Using Platform Events to rescue in Asynchronous Apex failures

Using Platform Events to rescue in Asynchronous Apex failures

Platform Events are fun and easy way for integrating Salesforce with external systems. But the usage is not limited to integration only. In this article, I want to share, how I used it to track down the failed asynchronous apex jobs.

Last week I was working on building a inline visualforce page for account object, where I can show the status of the running apex job. Unfortunately, I was not able to implement it properly. So i searched for pre-built solutions. Then I found the Salesforce batch apex documentation. Interestingly, Salesforce has built the solution to warn you, when some batch apex fails. That includes which records failed processing and for what reason.


Something you might want to know:

If you do not know, under setup, there is a setting which enables Salaesforce to send email notification if any exception occurs in apex (Search for Apex Exception Email), but it does not apply for those asynchronous apex jobs. You can monitor those under Apex Jobs though. But it is difficult to track, what records failed and why.

Batch Apex and Exception handling

Batch Apex is Salesforce native solution to handle large number of records. The Interface provides three methods to be implemented by the concrete class. The start method expects a Database.QueryLocator or Iterator to be returned. The QueryLocator can query 50millions records. In the execute method, you process the records returned from QueryLocator, but in chucks. Default chunk size is 200, but you can make it as high as 2000. The finish method runs after all records are processed. The start and finish methods run only once, where as the execute method runs multiple times depending on the chuck size and total records queried by QueryLocator. Each run has its own governor limits, meaning no two runs share the limits or transactions. Hence, suppose if 5 runs are expected to be run by the batch execution, and the 3rd run fails, other runs still will be performed. The errors encountered will not be surfaced, but you can see how many records were processed and how many failed under Apex Jobs for that particular Batch Apex Class.

Example:
public with sharing class BatchableApexExample implements Database.Batchable {
    public (Database.QueryLocator | Iterable) start(Database.BatchableContext bc) {

    }
    public void execute(Database.BatchableContext BC, list){

    }
    public void finish(Database.BatchableContext BC){

    }
}
                

Implementing Database.RaisesPlatformEvents

Now comes the fun part. How does Salesforce notifies about the exceptions then, that I talked earlier? It is again another interface Database.RaisesPlatformEvents. This interface fires platfrom events, whenver the batch execution encounters errors or exceptions. Events are also fired for Salesforce Platform internal errors and other uncatchable Apex exceptions such as LimitExceptions, which are caused by reaching governor limits. The event messages contain more detail about the exception, such as how often the event failed and which records were being processed that time.

Example:
public with sharing class BatchableApexExample implements Database.Batchable,
    Database.RaisesPlatformEvents {
    public (Database.QueryLocator | Iterable) start(Database.BatchableContext bc) {

    }
    public void execute(Database.BatchableContext BC, list){

    }
    public void finish(Database.BatchableContext BC){

    }
}
                

By subscribing to the BatchApexErrorEvent using a trigger and utilising a custom object, I achieved my goal. This was a way better Error Logging mechanism, than the one I was working on. Here is a snippet from my Batch Job error handling.


Example: Batch Apex Error Logging
trigger ErrorLogger on BatchApexErrorEvent (after insert) {
    Set asyncApexJobIds = new Set();
    for(BatchApexErrorEvent event:Trigger.new){
        asyncApexJobIds.add(event.AsyncApexJobId);
    }

    Map jobs = new Map(
        [SELECT id, ApexClass.Name FROM AsyncApexJob WHERE Id IN :asyncApexJobIds]
    );

    List logs = new List();
    for(BatchApexErrorEvent event : Trigger.new){
        if(jobs.get(event.AsyncApexJobId).ApexClass.Name == 'AccountAsyncService'){
            logs.add(
                new ErrorLog__c(
                    Scope__c = event.JobScope,
                    Origin__c = 'AccountAsyncService',
                    OriginType__c = 'Batch Apex',
                    ExceptionType__c = event.ExceptionType,
                    Message__c = event.Message,
                    StackTrace__c = event.StackTrace.replace('\n', ' ## ')
                )
            );
        }
    }
    insert logs;
}
                

Queueable Apex Exception Handling

For Queueable apex error logging, I reused the same ErrorLog__c object and created my own Platform Event AsyncApexErrorEvent__e. I kept it as similar as possible to the BatchApexErrorEvent. The trigger on that is also same as the above. In the Queueable class execute method I just added try catch blocks. In catch, I fire the AsyncApexErrorEvent__e and after that throw the error back, so that, the job fails. The trigger on the platform event then inserts an ErrorLog__c record.


Example: Queueable Apex Error Logging
public with sharing class AccountTransferService implements System.Queueable {
    public void execute(Id recordId) {
        /* Logic */
        try {
            update records;
        }
        catch(Exception e) {
            EventBus.publish(new AsyncApexErrorEvent__e(
                Origin__c = 'AccountTransferService',
                OriginType__c = 'Queueable Apex',
                ExceptionType__c = e.getExceptionType(),
                Message__c = e.getMessage(),
                StackTrace__c = e.getStackTraceString().replace('\n', ' ## '),
                Scope__c = recordId
            ));
            throw e;
        }
    }
}
                

Final Verdicts:

So this is how I used platform events to notify about exceptions encountered during asynchronous apex execution. I am still working on the inline visualforce to show the job status though. I am planning to reuse the same platform event and will share how it goes. Until next time, Happy Coding

Post a Comment

1 Comments

  1. This idea is cool and interesting. keep the new ideas coming :)

    ReplyDelete