Defensive future calls

I would like to talk about the wonderful and simple pattern for making future calls. This pattern was presented by Dan Appleman at one of his sessions at Dreamforce this year.

It looks like this:

public void CallFuture2()
{
if(System.isFuture() ||system.isBatch())
defensiveFutureCallSync();
else {
if(Limits.getFutureCalls() < Limits.getLimitFutureCalls())
defensiveFutureCallAsync();

else { // error handling

}

}
}

@future
public static void defensiveFutureCallAsync() {
defensiveFutureCallSync();
}

public static void defensiveFutureCallSync() {
// Do something here

}

Simple but very effective.

To provide you a quick example. Let’s suppose we have a Session object and a trigger which is fired when a Session record is inserted and we need to make a future call when it happens. In our future method we just insert a record in a Transaction object.

In order to do that avoiding issues could be as follow.

Trigger:

trigger SessionTrigger on Session__c (after insert) {
// trigger logic here...

// helper class which implements the pattern
TransactionLogHelper.insertLog(Trigger.new.size() + ' sessions were created');
}
Helper class which implements the pattern:

public class TransactionLogHelper {

public static void insertLog(String description)
{
if(System.isFuture() ||system.isBatch())
insertLogSync(description);
else {
if(Limits.getFutureCalls() < Limits.getLimitFutureCalls())
insertLogAsync(description);
else
// handle error logic
System.debug('Error handling...');
}

}
@future
public static void insertLogAsync(String description) {
insertLogSync(description);
}

public static void insertLogSync(String description) {
insert new Transaction_Log__c(timestamp__c = System.now(), description__c = description);
}

}

Remember you can pass complex parameters to future methods.

That’s enough. If a session is created from another future method or from a batch class, it also works!

How can we make sure? Let’s create a dummy batch for creating a session. However, this is not that straight forward. The start method that we have to write must return either Database.QueryLocator or Iterable.

Let’s move on with the Iterable option. We will write an Iterator and an Iterable implementation using it.

Iterator class:

global class SessionGenerator implements Iterator {

Integer Counter;

public SessionGenerator (Integer GeneratorLimit) {
Counter = GeneratorLimit;
}

global boolean hasNext() {
return Counter > 0;
}

global Session__c next() {
return new Session__c(Name='Test '+Counter--);
}

}
Iterable class:

global class SessionIterable implements iterable{

global Iterator Iterator(){
// it will create 1 session
return new SessionGenerator(1);
}

}

Finally, we already can create our batch 🙂

global class CreateSessionsBatch implements Database.batchable{

global Iterable start(Database.batchableContext info){
return new SessionIterable();
}

global void execute(Database.batchableContext info, List scope) {
insert scope;
}

global void finish(Database.batchableContext info){
}

}

We can fire our batch by using:

Database.executeBatch(new CreateSessionsBatch());

And it works as a charm. After firing the trigger a session is created and a transaction record as well.

Leave a Reply

avatar
  Subscribe  
Notify of