Skip to main content

Salesforce

Apex Queueable Jobs & Finalizers: Salesforce’s Power Duo

Sparkler 4629347 1920

Aloha Trailblazers!

In the world of Salesforce, data accuracy is super important. But sometimes, especially when dealing with lots of data or complicated processes, it can get tricky. That’s where Apex transaction finalizers come in handy. They’re like little helpers that let you do extra stuff after your data work is done. Thus helping you keep things in order.

What is the need for Apex Finalizers?

For developers familiar with Async Apex, although it provides a powerful set of tools tailored to different scenarios, a persistent challenge has existed: implementing retry logic for callouts and logging has proven difficult. Additionally, administrators and developers should be aware that monitoring async transactions can be done through the Apex Jobs tab on the Setup page. However, what if there’s a need for more granular control over these transactions?

For instance, Future methods, by design, do not return an Apex job ID. While these tools are beneficial, what if there’s a requirement for better logging and retry logic, aiming to reduce reliance on Batch Apex? This is where Queueable Apex and Finalizers step in. Before Transaction Finalizers, retrieving the ID of AsyncApexJob via SOQL and implementing retry logic in Queueable Apex were necessary. It’s worth noting that Batch Apex, with its limitations allowing only five concurrently running jobs per org, might not always suffice. Therefore, having enhancements to Queueable Apex as an alternative is invaluable. Queueables excel in handling simple Async Apex processing and making callouts to external systems with small data volumes. However, before Finalizers, tackling complex scenarios involving chained queueable jobs was cumbersome. If one job in the chain failed due to limit exceptions or other reasons, the entire chain had to be retried.

What do Apex Finalizers do?

Transaction Finalizers allow you to add actions to your Queueable Apex jobs using a special interface called Finalizer. After a job using Finalizer finishes running, whether it’s successful or encounters an error, the associated Finalizer code kicks in. This allows you to do things like tidy up or recover from any issues.

With Finalizers, you can do a bunch of useful tasks. For example, you can send email notifications to stay informed about what’s happening. You can also keep detailed logs for debugging and monitoring purposes. Additionally, Finalizers allow you to automatically retry Queueable jobs if they encounter errors. Furthermore, you can make external calls to other systems after database operations are done, which isn’t usually allowed in Apex.

This is pretty significant because making a callout after doing database operations isn’t normally allowed in Apex, but Finalizers provide a way around that limitation since they run after the Queueable job is completed. Finalizers don’t just kick in when a job fails; they also run when the job is completed successfully. This gives you a perfect opportunity to do extra tasks after the transaction is finished. For example, you could log important information, make additional API calls, or do any other post-transaction tasks you need to do.

Retry Queueable Example

This example, which I’ve borrowed from Salesforce documentation, showcases how to re-enqueue a failed Queueable job within its finalizer. It also highlights that jobs can be retried up to a maximum of five times, respecting the limit on queueable chaining.

public class RetryLimitDemo implements Finalizer, Queueable {
  public void execute(QueueableContext ctx) {
    String jobId = '' + ctx.getJobId();
    try {
        Finalizer finalizer = new RetryLimitDemo();
        System.attachFinalizer(finalizer);
        System.debug('Attached finalizer');
        Integer accountNumber = 1;
        while (true) { // results in limit error
          Account a = new Account();
          a.Name = 'Account-Number-' + accountNumber;
          insert a;
          accountNumber++;
        }
    } catch (Exception e) {
        System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
    } finally {
        System.debug('Completed: execution of queueable job: ' + jobId);
    }
  }

  // Finalizer implementation
  public void execute(FinalizerContext ctx) {
    String parentJobId = '' + ctx.getAsyncApexJobId();
    System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
    if (ctx.getResult() == ParentJobResult.SUCCESS) {
        System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
    } else {
        System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
        System.debug('Enqueueing another instance of the queueable...');
        String newJobId = '' + System.enqueueJob(new RetryLimitDemo()); // This call fails after 5 times when it hits the chaining limit
        System.debug('Enqueued new job: ' + newJobId);
    }
    System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
  }
}

 

Queueable Context: Within this context, we have our standard method execute(QueueableContext ctx) as required by the Queueable interface. This method serves as the entry point when the job gets added to the queue.

Finalizer Context: In addition, our code includes a method execute (FinalizerContext ctx) mandated by the Finalizer interface. This method is triggered upon the completion of the Queueable job, regardless of whether it succeeds or encounters an error.

It’s important to note that the queueable class implements the Finalizer interface, and by using System.attachFinalizer(finalizer);, we connect a Finalizer to the current Queueable job. This Finalizer gets executed once the Queueable job finishes, regardless of its outcome. We initialize a new finalizer instance by creating a new object: Finalizer finalizer = new RetryLimitDemo();.

In the execute method of the Queueable, if we encounter a limit error, we enqueue a new Queueable using the finalizer interface: String newJobId = System.enqueueJob(new RetryLimitDemo()). It’s that straightforward!

Implementation Details

  • Only one finalizer instance can be attached to any Queueable job.
  • You can enqueue a single asynchronous Apex job (Queueable, Future, or Batch) in the finalizer’s implementation of the execute method.
  • Callouts are allowed in finalizer implementations.
  • The Finalizer framework uses the state of the Finalizer object (if attached) at the end of Queueable execution. Mutation of the Finalizer state, after it’s attached, is therefore supported.
  • Variables that are declared transient are ignored by serialization and deserialization and therefore don’t persist in the Transaction Finalizer.

Conclusion

In Salesforce, Queueable jobs and Finalizers function within separate Apex and Database contexts. This unique setup allows Finalizers to execute even after encountering an uncaught error because they operate within their own execution context. As a result, the original Queueable class instance that instantiated the Finalizer no longer exists. Therefore, it’s best to refrain from attempting to call back into the Queueable class from the Finalizer.

I’m thrilled with this capability and anticipate its expansion to other aspects of the platform, such as Apex triggers. Presently, the ability to handle uncatchable Apex errors without incurring additional costs for Event Monitoring is incredibly valuable. Expanding this feature would lead to improved error handling and database management. As we look forward to potential enhancements, let’s express our gratitude to the Apex team for their outstanding work in recent years.

Feel free to explore the following blog links for more information:

Asynchronous JavaScript Essentials for Lightning Web Component (LWC)

How to Schedule an Apex Batch Class in Salesforce ?

Apex Finalizers | Salesforce Documentation

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Reena Joseph

Reena Joseph, our Senior Technical Consultant at Perficient, boasts 3.5 years of experience and holds the prestigious 3x Salesforce Certified title. Her trailblazing spirit is evident with 100 badges on Trailheads, showcasing her commitment to continuous learning. Not limited to Salesforce, Reena has also mastered SQL and Programming in HTML5 with JavaScript and CSS3 on Hacker Rank. Beyond certifications, her passion for staying abreast of technological advancements is seen in her avid reading habits. In the dynamic tech world, Reena Joseph stands ready to drive innovation and inspire with her dedication to excellence.

More from this Author

Categories
Follow Us