Queueable Apex : Loose Coupling with Chainable Interface
Intro to Queueable Apex
Time and again, we come across use cases where we have to either perform an extensive processing, which can breach governor limits, or we want to perform some work asynchronously for improved user experience. Queueable apex provides us a means to create an asynchronous job (An asynchronous job means a piece of work, which is scheduled to run in background, for e.g. you may want to do custom rollup calculations on opportunity save).
Queueable apex is similar to future methods, wherein your future method is queued within a server queue. Server picks up the job and executes it, based on resource availability. However, there are some differences, quite notably:
- Queueable apex returns an ID for further enquiry
- Queueable apex methods can invoke external API (if Queueable apex class implements Database.AllowsCallouts interface)
- Queueable apex methods can enqueue other queueable apex jobs
Say, we have a simple Queueable job, which performs some work,as below:
public class QueueableApexDemo implements Queueable {
String msg;
public QueueableApexDemo(String msg){
this.msg = msg;
}
public void execute(QueueableContext context) {
// Your processing logic here
system.debug(' This msg is = ' + msg);
}
}
To execute this Queueable Apex class, you can simply use following apex code (anonymous apex):
QueueableApexDemo a = new QueueableApexDemo('JOB_A');
system.enqueueJob(a);
Chaining Queueable Apex jobs
Now, let's say we have to chain two jobs, say we have to run another job, after completion of initial job.
So, we create another Queuable class, such as:
public class ChainedJob implements Queueable {
String msg;
public ChainedJob(String msg){
this.msg = msg;
}
public void execute(QueueableContext context) {
// Your processing logic here
system.debug(' ChainedJob msg is = ' + msg);
}
}
Now, in order to chain these, we'll need to modify initial class QueueableApexDemo as follows:
public class QueueableApexDemo implements Queueable {
String msg;
public QueueableApexDemo(String msg){
this.msg = msg;
}
public void execute(QueueableContext context) {
// Your processing logic here
system.debug(' This msg is = ' + msg);
ChainedJob job2 = new ChainedJob('CHAINED_JOB_1');
System.enqueueJob(nextJob);
}
}
Note: A key limitation to consider here is that Apex allows only one job to be enqueued via a Queueable job.
Tight coupling with Queueable Apex
Now, in real world, you would not want your first class (QueueableApexDemo) is tightly coupled with second class (ChainedJob). Also, in reality, as you create more complex code, there may be need to dynamically decide jobs to be chained together at runtime.
QueueableChainJob
I created a simple wrapper class to address this complexity around chaining queueable apex jobs at runtime, while keeping individual queueable apex classes loosely coupled. Another key design factor I kept in mind was to not add a complete wrapper on the Queueable apex class, rather extend its capabilities.
public abstract class QueueableChainJob {
private Queueable nextJob;
public void setNextJob(Queueable nextJob){
//Apex does not allow chaining queueable jobs within Test Context;
if(Test.isRunningTest() == false){
this.nextJob = null;
}
}
public Queueable getNextJob(){
return this.nextJob;
}
public ID enqueueNextJob(){
ID jobID = null;
if(nextJob != null){
jobID = System.enqueueJob(nextJob);
}
return jobID;
}
}
Implement Loose Coupling with Chainable Interface
Now, you can modify your Queuable Apex classes such as:
First class: QueueableApexDemo
public class QueueableApexDemo extends QueueableChainJob implements Queueable {
String msg;
public QueueableApexDemo(String msg){
this.msg = msg;
}
public void execute(QueueableContext context) {
// Your processing logic here
system.debug(' This msg is = ' + msg);
// Chain this job to next job by submitting the next job
ID jobID = this.enqueueNextJob();
}
}
Second Class: ChainJob
public class ChainedJob extends QueueableChainJob implements Queueable {
String msg;
public ChainedJob(String msg){
this.msg = msg;
}
public void execute(QueueableContext context) {
// Your processing logic here
system.debug(' ChainedJob msg is = ' + msg);
// Chain this job to next job by submitting the next job
ID jobID = this.enqueueNextJob();
}
}
Please note, there are minor differences:
- both classes now extend QueueableChainJob abstract class
- both classes have execute method calling enqueueNextJob();
Rest of the code remains as-is.
Now, you can run your code as follows:
QueueableApexDemo a = new QueueableApexDemo('JOB_A');
ChainedJob b = new ChainedJob('JOB_B');
a.setNextJob(b);
system.enqueueJob(a);
Similarly, you can chain multiple jobs as needed and add required conditions to add jobs. Sample code below:
QueueableApexDemo nextChainJob;
QueueableApexDemo a = new QueueableApexDemo('JOB_A');
nextChainJob = a;
if(UserInfo.getUserType() == 'Standard'){
ChainedJob b = new ChainedJob('JOB_B');
nextChainJob.setNextJob(b);
nextChainJob = b;
}
In above code, the second job is chained, only if current user's UserType is Standard, else second job is not chained.
The above code is meant for guidance is not tested for all scenarios.
Comments
Post a Comment