Triggers are great, they let you react to pretty much anything that happens to your records in Salesforce in also any way that you want.
There are a few things that are not so great about triggers though, the main two being:
Both of these can be overcome by leveraging the power of Process Builder and the @InvocableMethod
annotation to create Invocable Actions instead of using Triggers.
Invocable Actions are Apex methods that can be called from Flows, Processes and are exposed through the REST API.
To create an Invocable Action you need to add the @InvocableMethod
annotation to your method and away you go (OK, it’s not quite that simple, there are a few restrictions which are covered here).
The easiest way to explain anything is with an example, so here is one.
This example is going to be based around a fairly simple trigger for automatically assigning Entitlements to Cases (you may notice this is a cut down and tidied up version of the trigger in this help article):
trigger DefaultEntitlement on Case (before insert, before update) {
Set<Id> accountIds = new Set<Id>();
for (Case c : Trigger.new){
if (c.EntitlementId == null && c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
if(accountIds.size() > 0) {
List <Entitlement> entitlements = [
SELECT StartDate, EndDate, AccountId, AssetId
FROM Entitlement
WHERE AccountId in :accountIds
AND StartDate <= TODAY
AND EndDate >= TODAY
];
if(entitlements.size() > 0) {
Map<Id, Entitlement> entitlementsByAccountId = new Map<Id, Entitlement>();
for(Entitlement e : entitlements) {
entitlementsByAccountId.put(e.AccountId, e);
}
for(Case c : Trigger.new) {
if(c.EntitlementId == null && c.AccountId != null) {
Entitlement e = entitlementsByAccountId.get(c.AccountId);
if(e != null) {
c.EntitlementId = e.Id;
if(c.AssetId == null && e.AssetId != null) {
c.AssetId = e.AssetId;
}
}
}
}
}
}
}
public class DefaultEntitlementAction {
@InvocableMethod(label='Update Default Entitlements' description='Determines the default Entitlement for the Case and returns the updated Cases.')
public static void updateDefaultEntitlements(List<Case> casesToUpdate) {
List<Case> cases = [
SELECT AccountId, EntitlementId, AssetId
FROM Case
WHERE Id IN:new Map<Id, Case>(casesToUpdate).keySet()
];
Set<Id> accountIds = new Set<Id>();
for (Case c : cases){
if (c.EntitlementId == null && c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
if(accountIds.size() > 0) {
List <Entitlement> entitlements = [
SELECT StartDate, EndDate, AccountId, AssetId
FROM Entitlement
WHERE AccountId in :accountIds
AND StartDate <= TODAY
AND EndDate >= TODAY
];
if(entitlements.size() > 0) {
Map<Id, Entitlement> entitlementsByAccountId = new Map<Id, Entitlement>();
for(Entitlement e : entitlements) {
entitlementsByAccountId.put(e.AccountId, e);
}
for(Case c : cases){
if(c.EntitlementId == null && c.AccountId != null) {
Entitlement e = entitlementsByAccountId.get(c.AccountId);
if(e != null) {
c.EntitlementId = e.Id;
if(c.AssetId == null && e.AssetId != null) {
c.AssetId = e.AssetId;
}
}
}
}
update cases;
}
}
}
}
The first reason is that unlike Triggers, Invocable Actions don’t have all of the object’s fields available to them by default so we need to query to make sure that we have everything we need.
The second reason is that because Processes act like after
Triggers, in that the records passing through them (i.e. the records in Trigger.new
for triggers) are read-only. We need to requery to get non-read-only versions of our records so that we can change the field values.
The other main change is that we have to call update
on our Cases to actually update them. As we’ve queried for the records we need to tell Salesforce to actually commit the changes to the database, unlike in a before
trigger where that step is implicit for the records in Trigger.new
.
Now we have our Invocable Action we need to call it. In this case (badum tsh) we’re going to use the Process Builder to do this.
Create a new Process (call it whatever you like) and select Case as your object, and “when a record is created or edited” as your criteria for starting the Process.
Click the “Add Criteria” node, give your criteria a name and select “No criteriaโjust execute the actions!” as your Criteria for Executing Actions.
Then click “Add Action” under Immediate Actions and select “Apex” as the Action Type, give your action a name and then click in the “Apex Class” dropdown, hopefully you should see your class listed there, so select it!
One final step before we’re done, click “Add Row” in the Set Apex Variables section, under the Field dropdown you should have one option called “casesToUpdate”, select this, then click the value field and choose “Select the Case record that started your process”.
Your final process should look something like this:
All of this messing about with Process Builder may seem like a pain compared to just writing a trigger, but there are a few important advantages to this approach.
The biggest one is that your administrator is now in full control of if your logic runs, there may be a point in time where you no longer want to assign Entitlements automatically, now your administrator has the power to disable this.
In addition to this you can split the different bits of functionality in your triggers into multiple Invocable Actions to give your administrators the power to disable individual bits of functionality and change the order in which they execute.
You could (and probably should) go even further with this. At the moment our process has no criteria and the Apex handles which records should be processed. We could hand even more control to our admins with the added benefit of vastly simplify the Apex code by moving the criteria into the process instead.
As an added bonus, you get a couple of things for free with Invocable Actions. They can be reused by your administrators in other Processes and Flows, but more excitingly from a development perspective – they are automatically exposed by the Salesforce REST API!
It’s worth noting that there are a couple of limitations with taking this approach for all of your triggers.
The biggest limitation is that this approach does not work at all for delete
and undelete
triggers, the Process Builder can only run on record creation and updates, and I don’t see that changing any time soon.
The other limitation is that Processes execute quite late on in the Order of Execution. There are some scenarios (for example, building a compound key for a duplicate rule) that will still require a trigger as that is the only way to get them to execute at the right time.
These limitations shouldn’t discourage you using this approach where it does work though as it comes with a lot of advantages.
I’ve shown you how by using Invocable Actions and Process Builder instead of Triggers you can give power back to your administrators as well as getting some nice reusability for free.
There are a couple of limitations to the approach, but so long as your use-case falls outside of those you can get a lot of benefit for very little (or no) extra effort.
Next time you start to think about writing a Trigger, consider if it can be written as an Invocable Action instead. Future you will thank you for it.
Our independent tech team has been servicing enterprise clients for over 15 years from our HQ in Bristol, UK. Let’s see how we can work together and get the most out of your Salesforce implementation.