We have Apex code that needs to interrogate the schema to dynamically handle configuration-based behaviour (e.g. for defining fields that should be loaded or saved) for specific types of object (such as "Account" or "MyCustomObject__c") and have seen that a lot of CPU time is spent just loading the schema. What's the best way to use the Salesforce APIs to minimize the overheads?
The first thing to realize is that calls to Schema.getGlobalDescribe are cripplingly slow and should be avoided at (pretty much) all costs. The usual approach here is to replace this call with an appropriately targeted call to Schema.describeSObjects so you only load the schema detail for explicitly named object types (e.g. 'Account', 'MyCustomObject__c' etc.), though that has been shown as ineffective.
The second thing to realize is that Salesforce's Schema mechanism actually has two different flavours and two different session caches for this data. I am unsure what Salesforce officially calls these, but I have badged them:
- Dynamic - this is what is used when calling the Schema.getGlobalDescribe and Schema.describeSObjects methods or when using custom metadata Field Definition fields in queries
- Static - this is what is used when explicitly accessing schema elements via things like Schema.SObjectType.Account or Schema.SObjectType.MyCustomObject__c (and in a few other cases - see below).
Static schema access is much, much faster than dynamic access (literally orders of magnitude faster) and is the cornerstone for improving the performance you get.
While we can't always make use of Static schema access (see this question for an example) there are ways to avoid use of both Schema.getGlobalDescribe and Schema.describeSObjects when you already know the name(s) of the type(s) of SObject that you want to get the schema details for.
Given an SObject API name, such as "Account" or "MyCustomObject__c", as a string and realizing that the different types of SObject are actually Apex classes too, you can statically access the schema detail using the following (based on this question's answer):
SObject obj = (SObject) Type.forName(theApiNameString).newInstance(); DescribeSObjectResult describe = obj.getSObjectType().getDescribe();
The describe thus obtained does not even need to be added to a custom session cache (static map or similar) since Salesforce optimizes accessing that same describe again in the same session as part of its behind-the-API caching of results, and the reflective instantiation used is quite a cheap operation.
NB: Code that accesses the schema for a given object both "statically" and "dynamically" will have Salesforce load (and session cache behind the API) the schema details twice, as two different instances of the DescribeSObjectResult. For this reason it is best to ensure you always access the schema in the one way (ideally "statically").
Kudos to sfdcfox for identifying this alternative, Type-based approach.