Tweet |
Spring 20 Before Save Flows and Apex Triggers
Introduction
Spring 20 introduces the concept of the before record save flow, analogous to the before insert/update trigger that we’ve had for over a decade now. Like these triggers, the flow can make additional changes to the records without needing to save it to the database once it has finished its work. Avoiding this save makes things a lot faster - a claimed 10 times faster than doing similar work in process builder. What the release notes don’t tell us is how they compare to Apex triggers, which was the kind of thing I was a lot more interested in.Scenarios
- Changing the name of an opportunity to reflect things like the amount, close date. All activity happens on the record being inserted, so this is very simple.
- Changing the name of an opportunity as above, but including the name of the account the opportunity is associated with, so an additional record has to be retrieved.
Scenario 1
Flow
My flow is about as simple as it gets:The assignment appends '-{opportunity amount}' to the record name:
At the end of the transaction, I have the following limit stats:
Number of SOQL queries: 2 out of 100
Number of query rows: 1219 out of 50000
Maximum CPU time: 116 out of 10000
Trigger
The trigger is also very simple:1 2 3 4 5 6 7 | trigger Opp_biu on Opportunity (before insert, before update) { for (Opportunity opp : trigger. new ) { opp.name=opp.Name + '-' + opp.Amount; } } |
and this gives the following limit stats:
Number of SOQL queries: 2 out of 100
Number of query rows: 1219 out of 50000
Maximum CPU time: 1378 out of 10000
So in this case the trigger consumes over a thousand more milliseconds of CPU time. Depending on what else is going on in my transaction, this could be the difference between success and failure.
Scenario 2
Flow
There's a little more to the flow this time :Trigger
The trigger mirrors the flow, with a little extra code to ensure it is bulkified :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | trigger Opp_biu on Opportunity (before insert, before update) { Set<Id> accountIds= new Set<Id>(); for (Opportunity opp : trigger. new ) { accountIds.add(opp.AccountId); } Map<Id, Account> accountsById= new Map<Id, Account>( [select id, Name from Account where id in :accountIds]); for (Opportunity opp : trigger. new ) { Account acc=accountsById.get(opp.AccountId); opp.Name=acc.Name + '-' + opp.CloseDate + '-' + opp.Amount; } } |
which gives the following limit stats:
Maximum CPU time: 1773 out of 10000
Aside from telling us that CPU time isn't an exact science, as it went down this time, the flow is pretty much the same in spite of the additional work. The trigger, on the other hand, has consumed another 500 milliseconds.
All Flow All the Time?
So based on this, should all before insert/update functionality be migrated to flows? As always, the answer is it depends.One thing it depends on is whether you can do everything you need in the flow - per Salesforce Process Builder best practice:
For each object, use one automation tool.
It can also get really difficult to debug problems if you have your business logic striped across multiple technologies, especially if some aspects of it are trivial to change in production.
Something that is often forgotten with insert/update automation is what should happen when a record is restored from the recycle bin. In may ways this can be considered identical to inserting a new reecord. Triggers offer an after undelete variant to allow automated actions to take place - you don't currently have this option in the no code world.