Doubling Salesforce Governor Limits with Queueable and Platform Events

kwi 1, 2023

Salesforce Governor Limits are a fundamental aspect of Apex that differentiate it from other programming languages like Java or C#. These limits ensure that all calculations executed during a transaction use no more than 10 seconds of CPU time or 100 SOQL queries. Unfortunately, when the business demands more features and the code is already close to the limits, optimization is not always an option.

In such scenarios, using queueable Apex jobs is a recommended solution. Queueable jobs have double the governor limits compared to synchronous Apex. However, using queueable jobs has some limitations, including being asynchronous and lacking an easy way to return user results.

To overcome these challenges, we can combine Lightning Web Components (LWC), queueable Apex, and Platform Events to give the user a synchronous processing experience. In this article, we provide a real-life example of how to achieve this solution.

Let’s consider the following business scenario: Beth is a line manager at a bike and bike’s outfit manufacturer, overseeing 10 agents in a call center. Her subordinates answer technical questions about the company’s products and also have the opportunity to cross-sell additional products during the call. Beth needs to see her subordinates’ performance to motivate them, and real-time data is critical to doing so. However, KPI calculation is resource-intensive, requiring 103 SOQL queries, and cannot be optimized further.

As a system architect, you need to design a solution that meets the business needs while staying within Salesforce’s constraints. After discussing with your team, you decide to use queueable Apex and Platform Events. Rather than executing all actions in a single synchronous transaction, the LWC component calls the Apex controller, which then enqueues the queueable job that executes the heavy computation. Once the queueable job finishes executing, it publishes the results as a Platform Event. The LWC component that initiated the actions intercepts the published Platform Event.

To help you better understand this solution, let’s look at the component and sequence diagrams below:

Components Diagram:

Sequence Diagram

Lets go through components and try to understand what are they doing :


spa_KPIRealization – The LWC component that can be embedded on the user record page. During creation, the component subscribes to the platform event channel (/event/spa_AsyncResponse__e(javascript line 24)). It has a button that, upon being pressed, sends a request to the backend to recalculate subordinate KPIs(javascript line 14). The asynchronous result of the KPI recalculation is displayed using the event handler(javascript line 28).

LWC HTML

<template>
     <template if:true={isShowSpinner}>
        <div class="slds-is-relative">
            <lightning-spinner alternative-text="Loading" size="large"></lightning-spinner>
            <div class="slds-backdrop slds-backdrop_open"></div>
        </div>
    </template>
    <button onclick={calculateKPI}>Calculate KPI</button>
</template>
import { LightningElement, api } from 'lwc';
import { subscribe,unsubscribe } from 'lightning/empApi';	
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import calculateKPIForUser from '@salesforce/apex/spa_KPIRealizationController.calculateKPIForUser';

export default class Spa_KPIRealization extends LightningElement {
    @api recordId;
    subscription;
    isShowSpinner = false;

    async calculateKPI(){        
        try {       
            this.isShowSpinner = true;
            await calculateKPIForUser({ userId: this.recordId });            

        } catch (error) {
            this.isShowSpinner = false;
            console.error('Error during KPI Calculation:', error);          
        }
    }  

    connectedCallback() {
        // Subscribe to the Platform Event     
        this.subscription = subscribe(
            '/event/spa_AsyncResponse__e',
            -1,
            (event) => {
                this.handlePlatformEvent(event);
            }
        );
    }

    disconnectedCallback() {
        // Unsubscribe from the Platform Event
        unsubscribe(this.subscription);
        this.subscription = null;
    }

    handlePlatformEvent(event) {    
        // Extract the message from the Platform Event
        const orginRecordId = event.data.payload.RecordID__c;
        if(orginRecordId !== this.recordId){
            return;
        }
        const message = event.data.payload.Message__c;   
        this.isShowSpinner = false;
        const toastEvent = new ShowToastEvent({
            title: 'User KPI',
            message: message,
            variant: 'info',
        });
        this.dispatchEvent(toastEvent);
    }   
}

spa_KPIRealizationController – The Apex controller that exposes the calculateKPIForUserMethod. This method passes its parameters to the queueable job(line 5), which is then enqueued(line 6).

public class spa_KPIRealizationController {
    
    @AuraEnabled
    public static void calculateKPIForUser(Id userId){   
        spa_KPICalculationQueueable asyncKPICalculation = new spa_KPICalculationQueueable(userId);
        System.enqueueJob(asyncKPICalculation);  
    }

}

spa_KPICalculationQueueable – The Apex class that implements the queueable interface. This class calculates the user’s KPI(line 10) and publishes the results as a platform event (spa_AsyncResponse__e)(lines 26 to 30) that is intercepted by the spa_KPIRealization LWC component. Instead of displaying the KPI realization, the component displays the number of SOQL queries executed during the transaction.

public class spa_KPICalculationQueueable  implements Queueable  {

    private Id userId;

    public spa_KPICalculationQueueable(Id userId){
        this.userId = userId;    
    }

    public void execute(QueueableContext context) {
        String kpiRealization = calculateKPIRealization();
        publishResponse(kpiRealization);   
    }

    private String calculateKPIRealization(){
        //begin of heavy logic - calculation user KPI       
        (...)        
        //end of heavy logic - calculation user KPI
        String kpiRealization = Limits.getQueries() + ' out of ' + Limits.getLimitQueries() + ' SOQLs'; 
        // instead of KPI for the article purposes it returns number of SOQLs executed during transaction.
        return kpiRealization; 
        
    } 

    private void publishResponse(String kpi){
        
        spa_AsyncResponse__e asyncResponse = new spa_AsyncResponse__e(
            Message__c = kpi,
            RecordID__c = userId
        );
        EventBus.publish(asyncResponse);
    }

}

Let see how does it work:

1.Beth presses the „Calculate KPI” button.

2. Beth sees a rotating spinner.

3. The popup displays the number of SOQL queries used during the transaction instead of the KPI realization.

Summary:

this solution allows us to leverage all the features of queueable Apex, including executing 103 SOQL queries in a single asynchronous transaction. From the user’s perspective, the experience feels synchronous, even though it’s happening asynchronously on the backend. Of course there are also governor limits related with Queuable Job execution and Platform Event publishing, but these topics will be described in a separate articles.

Author: Bartosz Borowiec