I frequently advise basing Salesforce development decisions on platform capabilities, even if some features have not yet been embraced by your business. Keeping this in mind, I want to introduce the idea of running certain processes in both synchronous and asynchronous modes.
In a previous discussion, I highlighted a situation where Salesforce allowed Community Users to view their corresponding Contact records in search results but restricted them from making updates. This limitation led to frustration among Salesforce Community users. To address this, I proposed a solution involving a trigger that would transmit User value changes to the Community Contact record whenever someone edited their personal information within the Community.
As any Salesforce administrator or developer knows, it’s common for updates to be made to records in bulk, often through batch processing via Apex code. However, batches cannot initiate methods with the @Future annotation. Consequently, it’s essential to account for this platform restriction and enable the selective execution of methods in either synchronous or asynchronous modes.
One approach I’ve employed in the past to handle both synchronous and asynchronous logic is outlined below, and it may be applicable to your business or specific situation.
To begin, we require a class that allows the same method to be invoked either synchronously or asynchronously:
/* Created by: Greg Hacic Last Update: 1 March 2017 by Greg Hacic Questions?: greg@interactiveties.com Notes: - example for synchronous and asynchronous processing options */ public class communityUtil { //calls the handlePartnerUpdates method by passing a set of User record Ids @future //future annotation designates asynchronous execution public static void handlePartnerUpdatesFuture(Set<Id> userIds) { handlePartnerUpdates(userIds); //call the handlePartnerUpdates method and pass the Ids that were passed here } //populates the User details to the corresponding Contact for all passed User record Ids public static void handlePartnerUpdates(Set<Id> userIds) { List<Contact> contacts = new List<Contact>(); //list of Contact records to be updated for (User u : [SELECT City, ContactId, Country, Email, FirstName, LastName, Phone, PostalCode, State, Street, Title FROM User WHERE Id IN :userIds]) { //query for the User details if (!String.isBlank(u.ContactId)) { //if ContactId is not white space, empty (''), or null //provide all of the User details for the Contact update Contact c = new Contact(Email = u.Email, FirstName = u.FirstName, Id = u.ContactId, LastName = u.LastName, MailingCity = u.City, MailingCountry = u.Country, MailingPostalCode = u.PostalCode, MailingState = u.State, MailingStreet = u.Street, Phone = u.Phone, Title = u.Title ); contacts.add(c); //add the Contact to our list } } if (!contacts.isEmpty()) { //if the contacts list is not empty update contacts; //update the records } } //determines which method to call and passed the set of Ids to the winning method public static void handleMethodDecision(Set<Id> userIds) { if (!System.isFuture() && !System.isBatch()) { //if not invoked from future or batch context handlePartnerUpdatesFuture(userIds); //pass the User Ids to the populateHoursUsedFuture method for processing asynchronously } else { //otherwise, we are already in batch or future context handlePartnerUpdates(userIds); //pass the User Ids to the handlePartnerUpdates method for processing synchronously } } }
The class incorporates a method that leverages the system context to determine whether to execute the logic synchronously or asynchronously. We will invoke this method from a trigger associated with the User object, as depicted below:
/* Created by: Greg Hacic Last Update: 1 March 2017 by Greg Hacic Questions?: greg@interactiveties.com Notes: - example for synchronous and asynchronous processing options */ trigger updateContactAfterCommunityChanges on User (after update) { public Set<Id> userIds = new Set<Id>(); //set for holding all User Id keys for (User u : Trigger.new) { //for all records User o = Trigger.oldMap.get(u.Id); //grab the old values for the User if (u.City != o.City || u.Country != o.Country || u.Email != o.Email || u.FirstName != o.FirstName || u.LastName != o.LastName || u.Phone != o.Phone || u.PostalCode != o.PostalCode || u.State != o.State || u.Street != o.Street || u.Title != o.Title) { //if the City, Country, Email, FirstName, LastName, Phone, PostalCode, State, Street, Title changed userIds.add(u.Id); //add the Id to our set } } if (!userIds.isEmpty()) { //if the userIds set is not empty communityUtil.handleMethodDecision(userIds); //pass the set of Ids to the handleMethodDecision method } }
Conceptually, and mirroring the earlier post, the integrated logic remains straightforward. If specific attributes of any Salesforce Community Users are modified, we proceed to update all Contact records linked to those Users. Moreover, the execution of this logic can be tailored based on system context and resource availability.
Certainly, not all processes can embrace the framework outlined here. However, consider this approach as you collaborate with your development team on the Salesforce platform moving forward.