Salesforce: A Practical Approach to Queueables

Salesforce triggers are great, but they run synchronously by default. What if you want to speed up the user experience and run noncritical tasks in the background? That’s what Queueable is for. But it’s not as simple as it’s made out to be.

I think most organizations have a simple process where they have a Trigger that calls a Trigger Handler. With a Queueable, you might think you can do something like this.

Trigger:

    if (Trigger.isBefore && Trigger.isUpdate) {
        ContactTriggerHandler.fireMyProcess(Trigger.oldMap, Trigger.newMap);
    } 

Trigger Handler:

public static void fireMyProcess(Map<Id,Contact>; oldMap, Map<Id,Contact>; newMap)
    {
        List<Contact> contacts = new List<Contact>();
        for (Contact c : newMap.values())
            if (should be processed asynchronously)
                contacts.add(c);

        jobId = System.enqueueJob(new ContactTriggerHandlerQueueable(contacts)); 
    }

Then a Queueable to implement your business logic.

 public class ContactTriggerHandlerQueueable implements Queueable {

        public ContactTriggerHandlerQueueable(List&lt;Contact&gt; contacts)
        {
            // Save off my Contacts into a class variable
        }

        public void execute(QueueableContext context) {
            // My business Logic
        }
    }

The problem with this approach is, as Brian Fear (aka sfdcfox) said in his outstanding response to my StackExchange Question: “The rule is that if you’re synchronous, you get 50 jobs for that transaction. Once you go asynchronous, you get only one child allowed.”

Brian went on to provide an example, which works great. Basically it detects if you’ve “Gone Synchronous” and runs the Queueable appropriately.

Every time I hear “Gone Synchronous” it makes me think of this.

This Works Great.. But

The issue you’ll soon run into is that you can’t chain Queueables in test classes. This is maddening, really. They give you this cool way to do asynchronous processing, but cut you off at the knees in mandatory test code.

The workaround is to detect if you are working in test, and run the Queueable synchronously instead.

if (!Test.isRunningTest()  || isBulkTest)
		{
			QueueableUtilities.enqueueJob(new ContactTriggerHandlerQueueable(a, b, c, d));
		else
		{
			 ContactTriggerHandlerQueueable ctq = new ContactTriggerHandlerQueueable(a, b, c, d);
			ctq.execute();
		}

The only issue with this is that synchronous processing gets lower limits than asynchronous/queueable, which I found out when my bulk test code ran afoul of a CPU time limit. So in this case I created a public static Boolean variable in my Trigger Handler that I can set explicitly from the test class, that forces the test to run as a Queueable, above as isBulkTest. You just need to be careful that downstream chaining doesn’t do the same thing from the same test method.

Please let me know if this is helpful or if you have any questions. You can reach me at @BobHatcher on your friendly Twitter machine.

Leave a Reply

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