← Back to list

Scalable and Ordered Queueable Execution from Triggers in Salesforce

🚀 Problem Statement

Rohit Maharashi · 2025-06-26 06:10 · 3 claps · 2.6 min read
#salesforce-development #salesforce-asynchronous #salesforce-apex
Open on Medium ↗
Wiki topics: CRM · Email & CRM

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 critical platform limitation:

When a transaction is already running asynchronously (e.g., from another Queueable/Future/Batch), only one Queueable 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 :

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

🔧 Core Components

1. BaseChainedQueueable

An abstract base class that supports chaining:

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();
}

2. 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);
          //Explicity blank out nextJob to ensure JobC doesn't enqueue both JobD and JobC1
          nextJob = null;
        }
        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.

3. State Class

Holds the intermediate state from the trigger:

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); }
}

4. QueueableOrchestrator

Safely enqueues the job chain entry point depending on 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);
        }
    }
}

5. JobQueueManager

Central place to build and chain all jobs:

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);
    }
}

6. 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 strict sequence, even with internal chains.
  • 🚪 Governor Safe: Only one enqueueJob() per async context.
  • 💪 Modular: Each job is encapsulated and testable.
  • Reusable: Extendable across any object’s trigger.
  • ⚖️ Input-Aware: Jobs can accept multiple inputs from a central State object.

📚 Closing the Loop

This design abstracts away common platform limitations while providing a clean, object-oriented framework for orchestrating queueables from triggers. Whether you’re scaling post-processing across multiple objects or introducing internal chains, this pattern allows you to do so predictably, safely, and cleanly.

Use this as your go-to template when you need multi-step asynchronous workflows driven by trigger events.


메타데이터
post_id
421bfb3aa19c
slug
scalable-and-ordered-queueable-execution-from-triggers-in-salesforce-421bfb3aa19c
url
https://medium.com/@rmaharashi/scalable-and-ordered-queueable-execution-from-triggers-in-salesforce-421bfb3aa19c
canonical_url
https://medium.com/@rmaharashi/scalable-and-ordered-queueable-execution-from-triggers-in-salesforce-421bfb3aa19c
author_url
https://medium.com/@rmaharashi
status
ok
fetched_at
2026-08-19 05:11:59