Processes That Occur Asynchronously On The Salesforce Platform.


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.