[SalesForce] Design Patterns – Singleton applied to Apex w/o describe call limits

I've been studying Design Patterns. A Singleton pattern is described as "minimizing object instantiation for improved performance and to mitigate impact of governor limits."

In the Technical Library doc Apex Design Patterns under Singleton, it says the following.

Common uses for this pattern include:

  • Global variables – whilst Apex does not support global variables across execution contexts, this pattern allows you to create an object instance that will only ever be instantiated once within an execution context.

  • Limiting Impact of Governor Limits – certain system objects and methods, such as Apex Describes, are subject to governor limits. The Singleton pattern allows repeated reference to these without breaching governor limits.

  • As an implementation to other patterns – other design patterns, such as Facade, are often implemented as Singletons.

However, it's most common use is to create an object instance that's instantiated only once for the lifetime of that execution context.

In the implementation, it goes on to give an example of a trigger that calls a class that uses a describe call to instantiate a record type for an Account.

The Question:

Now that Governor Limits no longer exist on describe calls (describe call limits were 100 per execution context when article was written), other than speed from not having to make the additional calls, what will be the primary benefits in the context of Apex for using the Singleton Design pattern?

I can see where having a query or describe call having to only run once and then essentially being cached in a variable offers a lot of potential for speed advantages and reduced memory when processing code, but clearly the specific governor limits that previously were cited as drivers for using this pattern in an APEX context no longer exist on the platform.

Are there other governor limits that still exist where the Singleton pattern is of particular value or other use cases where it's especially advantageous or frequently used?

For reference:

The Trigger

trigger AccountTrigger on Account (before insert, before update) {
    for(Account record : Trigger.new){
        // Instantiate the record type using the singleton class
        AccountFooRecordType rt = AccountFooRecordType.getInstance();
        ....
    }
}

The Singleton Class

public class AccountFooRecordType {
    // private static variable referencing the class
    private static AccountFooRecordType instance = null;
    public String id {get;private set;} // the id of the record type

    // The constructor is private and initializes the id of the record type
    private AccountFooRecordType(){
        id = Account.sObjectType.getDescribe()
            .getRecordTypeInfosByName().get('Foo').getRecordTypeId();
    }
    // a static method that returns the instance of the record type
    public static AccountFooRecordType getInstance(){
        // lazy load the record type - only initialize if it doesn't already exist
        if(instance == null) instance = new AccountFooRecordType();
        return instance;
    }
}

Best Answer

Singleton designs are never as efficient as static variables, which are not as efficient as local (method) variables. Do feel free to use static variables to cache common queries, but do not use them to cache unmetered results, such as describe calls. Depending on the object being described, it can actually increase the CPU time used.

As an example, I give you this:

public class AccountSObjectType {
    static DescribeSObjectResult instance;
    public static DescribeSObjectResult getDescribe() {
        if(instance == null) instance = Account.SObjectType.getDescribe();
        return instance;
    }
}

In my org, I ran this for a baseline:

Long start = DateTime.now().getTime();
for(Integer i = 0; i < 100000; i++) {
    DescribeSObjectResult recordTypeId = Account.SObjectType.getDescribe();
}
Long stop = DateTime.now().getTime();
System.assert(false, stop-start);

The result was about 1900 milliseconds. I figured that using the singleton should improve my performance, right?

Long start = DateTime.now().getTime();
for(Integer i = 0; i < 100000; i++) {
    DescribeSObjectResult recordTypeId = AccountSObjectType.getDescribe();
}
Long stop = DateTime.now().getTime();
System.assert(false, stop-start);

The first run result of that was about 3800 ms (subsequent runs got no lower than about 2400 ms). That's right. 50-100% more CPU time than the direct call. The Singleton actually failed to outperform a describe called directly from a Schema object.

So, use the Singleton as a convenience tool (e.g. gather data all in one place), but do not always expect it to give you a boost in performance.

Related Topic