Platform Cache in Code

So, as I mentioned in Deep dive in to Platform Cache, that the platform cache has an advantage over the type of data that can be added to platform cache., such as collections (map, list, sets),
Custom settings are like custom objects and can only have specific field types. To access the custom settings, we use the CustomSettingName.getinstance(‘Instrance name’) method and it returns one record.
On the other hand, Platform cache can carry a list, map of multiple records, etc. which I will demonstrate here. In this example, we have the following:

  1. two Apex classes: classInitiatePC and classCheckPC
  2. two Visualforce Pages initiatePC’ and CheckPlatformCacheVariable

In class ‘classInitiatePC’ constructor, we initiate a cache variable ‘accountOpty’ , which contains the map of Germany based accounts with open opportunities. This ‘accountOpty’ is a Map<Account,List<Opportunity>> type. This cached variable could have been initiated anywhere in the application, in another class, a custom home page initialization, or if it is an org cache variable then in any other session by another user as well. I put it in constructor just for the ease of understanding of this example. This class is the controller of a Visualforce page name ‘initiatePC’ which contains a button ‘Get Cached Account Opty’and calls the ‘getAccountOpportunitiesFromCache()’ method. On the click of this button, the function returns the cached opportunity and the SOQL statement is not required.

code: classInitiatePC

	/**
	 * Created by darya on 7/11/2018.
	 */

	public with sharing class classInitiatePC{
		//public Map<Id,Account&amp;gt; accountMap;
		public Map<Account,List<Opportunity>> accountOpportunitiesMap {get; set;}

		public classInitiatePC(){
			List<Account> accounts= [SELECT Id, Name, BillingCountry, (SELECT Id, Name, Amount,CloseDate, Account.Name FROM Opportunities) FROM Account WHERE BillingCountry='Germany'];
			Map<Account,List<Opportunity>>accountOpty = new Map<Account, List<Opportunity>>();
			for( Account account : accounts )
			{
				if( !account.Opportunities.isEmpty() )
				{
					accountOpty.put(account,account.Opportunities);
				}
			}
			Cache.Session.put('accountOpty', accountOpty);
		}

		public PageReference getAccountOpportunitiesFromCache(){
			accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get('accountOpty');

			return null;
		}

	}

Visualforce Page: initiatePC 

	<!--
	 - Created by darya on 7/18/2018.
	 -->

	<apex:page id="initiatePC" controller="classInitiatePC">
		<apex:form>
			<apex:pageBlock id="block" mode="edit"  title="Platform Cache Example">
				<apex:pageBlockSection columns="1">
					<apex:pageBlockSectionItem >
						<apex:commandButton  action="{!getAccountOpportunitiesFromCache}" value="Get Cached Opty" reRender="target"/>
					</apex:pageBlockSectionItem >
				</apex:pageBlockSection>
				<apex:pageBlockSection  columns="2">
					<apex:pageBlockSectionItem >
					   <apex:outputpanel id="target">
						   <apex:pageBlockTable value="{!accountOpportunitiesMap}" var="mapAccKey">
							   <apex:column value="{!mapAccKey.Name}"/>
							   <apex:column >
								   <apex:pageBlockTable value="{!accountOpportunitiesMap[mapAccKey]}" var="opty">
									   <apex:column value="{!opty.Id}"/>
									   <apex:column value="{!opty.Name}"/>
									   <apex:column value="{!opty.CloseDate}"/>
									   <apex:column value="{!opty.Amount}"/>
								   </apex:pageBlockTable>
							   </apex:column>
						   </apex:pageBlockTable>
						</apex:outputpanel>
					</apex:pageBlockSectionItem >
				</apex:pageBlockSection>
			</apex:pageBlock>
		</apex:form>
	</apex:page>

	

To show how easily cache is available on other pages and class as well, we will add a button ‘Go To Next Page’ and a function ‘gotoNextPage()’ which will take the user to another visualforce page ‘CheckPlatformCacheVariable’.
As we called the cached variable in our first page, we can get the cached variable on this page controller ‘classCheckPC’ and display the results.

code: classCheckPC

	/**
	 * Created by darya on 7/20/2018.
	 */

	public with sharing class classCheckPC{

		public Map<Account,List<Opportunity>> accountOpportunitiesMap {get; set;}

		public classCheckPC(){
			accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get('accountOpty');
		}

	}

	

Visualforce page: CheckPlatformCacheVariable

	<apex:page id="CheckPlatformCacheVariable" controller="classCheckPC">
		<!--<c:cacheRetrieve pcMap="{!accountsFromCache}" />-->
		<apex:form>
			<apex:pageBlock mode="edit" id="block">
				<apex:pageBlockSection  columns="2">
					<apex:pageBlockSectionItem >
						<apex:outputpanel>
							<apex:pageBlockTable value="{!accountOpportunitiesMap}" var="mapAccKey">
								<apex:column value="{!mapAccKey.Name}"/>
								<apex:column >
									<apex:pageBlockTable value="{!accountOpportunitiesMap[mapAccKey]}" var="opty">
										<apex:column value="{!opty.Id}"/>
										<apex:column value="{!opty.Name}"/>
										<apex:column value="{!opty.CloseDate}"/>
										<apex:column value="{!opty.Amount}"/>
									</apex:pageBlockTable>
								</apex:column>
							</apex:pageBlockTable>
						</apex:outputpanel>
					</apex:pageBlockSectionItem >
				</apex:pageBlockSection>
			</apex:pageBlock>
		</apex:form>
	</apex:page>

Now, let’s assume for some reason the cache is missed or does not exist, and when we try to access it, the system will give a null pointer error. So, we need to plan for fail-safe. In such situation, while getting the cache variable in the constructor we can check if the cached variable is null, then recalculate the cache variable and return. As I mentioned in the last article, platform cache should be used for easily reconstruct-able data only.
The ‘getAccountOpportunitiesFromCache()’ method of ‘classInitiatePC’ class and the constructor of ‘classCheckPC’ class will be modified as below:

	public PageReference getAccountOpportunitiesFromCache(){
		if(Cache.Session.get('accountOpty') != null){
			accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get('accountOpty');
		}
		else{
			Map<Account,List<Opportunity>> accountOpty = new Map<Account, List<Opportunity>>();
			for( Account account : [SELECT Id, Name, BillingCountry, (SELECT Id, Name, Amount,CloseDate, Account.Name FROM Opportunities) FROM Account WHERE BillingCountry='Germany'] )
			{
				if( !account.Opportunities.isEmpty() )
				{
					accountOpty.put(account,account.Opportunities);
				}
			}
			accountOpportunitiesMap = accountOpty;
			Cache.Session.put('accountOpty', accountOpty);
		}

		return null;
	}

Another fail-safe idea would be to implement ‘Cache.Builder interface’ and use ‘doLoad()’ method. This interface method is called automatically if the called cache variable does not exist and it will reconstruct the data and pass it on. The overridden doLoad method takes the string parameter as the key and always return the data to be stored on the cache variable. And this reduces the code to a significant amount as fail safe is maintained just at one place.
Please note, the getter for the cached variable, here we are referring to class where the cached variable is initiated.

	accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get(classInitiatePC.class,'accountOpty');

Modified classInitiatePC class

	/**
	 * Created by darya on 7/11/2018.
	 */

	public with sharing class classInitiatePC implements Cache.CacheBuilder{
		//public Map<Id,Account> accountMap;
		public Map<Account,List<Opportunity>> accountOpportunitiesMap {get; set;}

		public Object doLoad(String name) {
			List<Account> accounts= [SELECT Id, Name, BillingCountry, (SELECT Id, Name, Amount,CloseDate, Account.Name FROM Opportunities) FROM Account WHERE BillingCountry='Germany'];
			Map<Account,List<Opportunity>> accountOpty = new Map<Account, List<Opportunity>>();
			for( Account account : accounts )
			{
				if( !account.Opportunities.isEmpty() )
				{
					accountOpty.put(account,account.Opportunities);
				}
			}
			return accountOpty;
		}

		public classInitiatePC(){

		}

		public PageReference getAccountOpportunitiesFromCache(){
			accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get(testDevvratArya.class,'accountOpty');
			return null;
		}

		public PageReference gotoNextPage(){
			PageReference pageRef = new PageReference('/apex/CheckPlatformCacheVariable');
			return pageRef;
		}

	}

	

classCheckPC class

	/**
	 * Created by darya on 7/20/2018.
	 */

	public with sharing class classCheckPC{

		public Map<Account,List<Opportunity>> accountOpportunitiesMap {get; set;}

		public classCheckPC(){
			accountOpportunitiesMap = (Map<Account,List<Opportunity>>) Cache.Session.get(classInitiatePC.class,'accountOpty');

		}
	}

That’s all for platform cache, if you have any questions, please drop your question in the comments, I am happy to answer.
I would suggest these further readings.
1. https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_platform_cache_best_practices.htm
2. https://salesforce.stackexchange.com/questions/98887/org-cache-vs-custom-settings
3. https://developer.salesforce.com/blogs/engineering/2015/05/platform-cache.html
4. https://trailhead.salesforce.com/en/modules/platform_cache

Leave a Reply