⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce

🚀 Problem Statement

In Salesforce, Apex triggers often need to perform post-commit processing or heavy logic that exceeds synchronous limits. Queueable Apex jobs are the go-to solution for such asynchronous processing. However, there’s a cri…


This content originally appeared on DEV Community and was authored by Rohit Maharashi

🚀 Problem Statement

In Salesforce, Apex triggers often need to perform post-commit processing or heavy logic that exceeds synchronous limits. Queueable Apex jobs are the go-to solution for such asynchronous processing. However, there’s a critical platform limitation:

When a transaction is already running asynchronously (e.g., from another Queueable/Future/Batch), only one System.enqueueJob() call is allowed.

This makes it difficult to:
• Enqueue multiple jobs from a trigger
• Maintain ordered execution of those jobs
• Support nested internal chaining (e.g., Job C triggers C1 and C2)
• Handle complex input data, possibly from the trigger state
• Build a framework that works for any object

🛠️ Design Intent

We aim to build a generic, scalable, and governor-safe Apex framework to:
• Enqueue multiple Queueable jobs in order
• Allow jobs to chain additional sub-jobs internally
• Support input injection from a State object
• Work in both synchronous and asynchronous contexts
• Be reusable across object trigger handlers

The final flow will look like this:

Trigger.afterInsert() ➔ A ➔ B ➔ C ➔ C1 ➔ C2 ➔ D ➔ E

🔧 Core Components

  1. BaseChainedQueueable

An abstract base class to support chaining jobs one after another.

public abstract class BaseChainedQueueable implements Queueable {
    protected BaseChainedQueueable nextJob;

    public void setNext(BaseChainedQueueable nextJob) {
        this.nextJob = nextJob;
    }

    public void execute(QueueableContext context) {
        run();
        if (nextJob != null) {
            System.enqueueJob(nextJob);
        }
    }

    public abstract void run();
}

  1. Sample Job Classes
public class JobA extends BaseChainedQueueable {
    private List<Account> accounts;

    public JobA(List<Account> accounts) {
        this.accounts = accounts;
    }

    public override void run() {
        System.debug('Running A');
    }
}
public class JobC extends BaseChainedQueueable {
    private List<Contact> contacts;

    public JobC(List<Contact> contacts) {
        this.contacts = contacts;
    }

    public override void run() {
        System.debug('Running C');
        JobC1 c1 = new JobC1();
        JobC2 c2 = new JobC2();
        c1.setNext(c2);

        if (nextJob != null) {
            c2.setNext(nextJob);
        }

        System.enqueueJob(c1);
    }
}
public class JobC1 extends BaseChainedQueueable {
    public override void run() {
        System.debug('Running C1');
    }
}

public class JobC2 extends BaseChainedQueueable {
    public override void run() {
        System.debug('Running C2');
    }
}

Repeat similarly for JobB, JobD, and JobE.

  1. State Class

A generic key-value store to pass contextual data between jobs:

public class State {
    private Map<String, Object> data = new Map<String, Object>();

    public void put(String key, Object value) {
        data.put(key, value);
    }

    public Object get(String key) {
        return data.get(key);
    }
}

  1. QueueableOrchestrator

Handles safe job submission based on execution context:

public class QueueableOrchestrator {
    public static void run(BaseChainedQueueable firstJob) {
        if (AsyncUtils.isAsync()) {
            System.enqueueJob(new ChainedStarter(firstJob));
        } else {
            System.enqueueJob(firstJob);
        }
    }

    private class ChainedStarter implements Queueable {
        private BaseChainedQueueable job;

        public ChainedStarter(BaseChainedQueueable job) {
            this.job = job;
        }

        public void execute(QueueableContext context) {
            System.enqueueJob(job);
        }
    }
}

  1. JobQueueManager

Central place to build and connect the job chain:

public class JobQueueManager {
    public static void runChainedJobsFromState(State state) {
        List<Account> accounts = (List<Account>) state.get('accounts');
        List<Contact> contacts = (List<Contact>) state.get('contacts');
        List<Opportunity> opps = (List<Opportunity>) state.get('opps');

        JobA a = new JobA(accounts);
        JobB b = new JobB(opps);
        JobC c = new JobC(contacts);
        JobD d = new JobD();
        JobE e = new JobE();

        a.setNext(b);
        b.setNext(c);
        c.setNext(d);
        d.setNext(e);

        QueueableOrchestrator.run(a);
    }
}

  1. Trigger Handler Integration
public class AccountTriggerHandler extends TriggerHandler {
    public override void afterInsert() {
        State state = new State();
        state.put('accounts', (List<Account>) Trigger.new);

        List<Id> accIds = new Map<Id, Account>(Trigger.new).keySet();
        state.put('contacts', ContactSelector.getByAccountIds(accIds));
        state.put('opps', OpportunitySelector.getByAccountIds(accIds));

        JobQueueManager.runChainedJobsFromState(state);
    }
}

✅ Benefits Recap

•🔁 Fully Ordered Execution — Jobs execute in defined sequence, even with sub-jobs.
•🧱 Governor Safe — One enqueueJob() per async context.
•🧪 Modular and Testable — Each job is self-contained and reusable.
•🔄 Reusable Architecture — Extendable to any object’s trigger.
•🧠 Input-Aware — Jobs receive input from a shared State.

📚 Closing the Loop

This pattern abstracts away common Salesforce platform limitations, providing a clean, object-oriented framework for managing chained asynchronous logic from Apex triggers.

Whether you’re scaling post-processing across multiple SObjects or introducing conditional sub-flows, this solution lets you build predictably, safely, and cleanly.


This content originally appeared on DEV Community and was authored by Rohit Maharashi


Print Share Comment Cite Upload Translate Updates
APA

Rohit Maharashi | Sciencx (2025-06-27T03:04:59+00:00) ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce. Retrieved from https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/

MLA
" » ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce." Rohit Maharashi | Sciencx - Friday June 27, 2025, https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/
HARVARD
Rohit Maharashi | Sciencx Friday June 27, 2025 » ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce., viewed ,<https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/>
VANCOUVER
Rohit Maharashi | Sciencx - » ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/
CHICAGO
" » ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce." Rohit Maharashi | Sciencx - Accessed . https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/
IEEE
" » ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce." Rohit Maharashi | Sciencx [Online]. Available: https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/. [Accessed: ]
rf:citation
» ⚙️ Scalable and Ordered Queueable Execution from Triggers in Salesforce | Rohit Maharashi | Sciencx | https://www.scien.cx/2025/06/27/%e2%9a%99%ef%b8%8f-scalable-and-ordered-queueable-execution-from-triggers-in-salesforce/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.