Asynchronous Processes on the Salesforce Platform
I often recommend making Salesforce development decisions based upon platform capabilities even if some of those capabilities have not yet been adopted by your business. With this in mind, I'd like to share the concept of being able to run some processes both synchronously and asynchronously.
I once wrote about how Salesforce allows Community Users to see their corresponding Contact record in search results but prevents them from making updates to it. At the time this was unnecessarily frustrating for the people that were using our Salesforce Community so I offered up a potential solution whereby a trigger would pass User value changes to the Community Contact record whenever the person edited their personal information within the Community.
As any Salesforce administrator or developer will tell you, it is commonplace for updates to be made to records in mass. This may be accomplished in a multitude of ways including batch processing via Apex code. Batches are prevented from kicking off methods with @Future annotation. So you should accommodate this platform limitation by allowing for situational processing of the same methods either synchronously or asynchronously.
Here is one way to handle synchronous or asynchronous logic that I have used in the past, which might make sense for your business or situation.
First, we need a class that allows for 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
}
}
}
Included in the class is a method where we use the system context to make a decision whether to process the logic synchronously or asynchronously. This method is the one we will call from a User object trigger, which is 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 like the similar post, the combined logic is rather simple. If certain attributes of any of our Salesforce Community Users is changed then we update all Contact records corresponding to those Users. Furthermore, this logic can be run appropriately based upon system context and resource availability.
Of course, not all processes can adopt the type of framework I have outlined here. But give it some thought as you move forward with your development team on the Salesforce platform.