Recently, the Salesforce Apex Trigger Action Framework appeared in one of my feeds so I took a closer look. It was originally created by Mitchell Spano and he provides an excellent overview on Apex Hours.
This framework is a metadata-driven Trigger framework that lets one dynamically instantiate the specified Trigger Actions for an Object when records are inserted, updated, deleted, or undeleted. Each trigger action is per event such as before insert, before update, before delete, after insert, after update, etc. Also, each trigger action can be implemented with an Apex class that implements the appropriate TriggerAction interface or it can be a Flow that follows the necessary inputs and output that the framework expects.
- Single Trigger Per Object – This is a general best practice that every Apex Trigger framework must follow.
- New Trigger Actions Are Easy – Implementing a new trigger action is as simple as implementing the appropriate interface and configuring the custom metadata. This keeps the code loosely decoupled and easily changed.
- Multiple Bypass Options – One can bypass all trigger actions for an entire object or for one or more specific trigger actions by configuring the custom metadata records. One can also bypass the actions using custom permissions specified in the custom metadata records. One can also programmatically disable an entire object or one or more specific trigger actions for a specific transaction. This is very helpful when needed such as during data imports or integrations.
- Recursion Prevention – The framework keeps track of how many times a record is seen and stops it from being processed again. This guy prefers managing the recursion per feature (trigger action) instead of relying on a framework because there are times when it should run again and the framework preventing it from running again may cause issues.
- Trigger Action Order – The order in which the trigger actions run is specified in the custom metadata.
Flow Trigger Action Closer Look
Instantiating a Flow from Apex has been around a while but has not been widely used in my experience. So I wanted to see how the framework uses a flow for a trigger action. It essentially passes the list of records to the flow using a flow input collection variable. The flow then does something with the records. This works well if the number of records is low but does not scale because a single flow interview is limited to executing only 2,000 elements!
In a Trailhead org, the Trigger Action Flow Test flow was augmented to use 12 nodes per record iteration and the test code was changed to provide 200 records. The test failed with the “System.FlowException: Number of iterations exceeded” error.
Because of that and Salesforce making record-triggered flows easy to manage on the platform, flow trigger actions should be avoided!
Nitpicks & Quibbles
- Programmatic Bypass API is Odd – The MetadataTriggerHandler, TriggerHandlerBase and TriggerActionFlow classes each have bypass, clearBypass, isbypassed, and clearAllBypasses static methods in them. The TriggerHandlerBase uses those methods for the “Object Bypass”. The MetadataTriggerHandler uses them for the “Trigger Actions Bypass”. I think it would’ve been better to put them in a separate class for each. For example, the “ObjectTriggerBypass” and the “TriggerActionBypass” apex classes could be used by the framework. Alternatively, the methods could be more qualified like clearObjectBypass, bypassObject, bypassTriggerAction, and clearTriggerActionBypass.
- Trigger Action Too Granular – The trigger action is too granular at the event level such as before insert and before update. It is better if the Trigger Action was a virtual class with all the possible trigger events as empty virtual methods that the implementer could override as needed. This would greatly simplify the trigger framework code and simplify the custom metadata by only having to specify one Object custom metadata record instead of up to 7. Specifying different Object custom metadata records for each event is possible but a real reason why it would be used escapes me. The tradeoff is that the empty event methods may get executed when not needed but that will be very fast and negligible.
- TriggerBase Seems Unnecessary – The TriggerBase seems unnecessary and could be included in the MetadataTriggerHandler. Its run method downcasts itself to the appropriate TriggerAction event and then calls the appropriate event handler. Downcasting has always been a code smell for me that the code should be refactored. If the TriggerAction was a virtual class, the run method on the MetadataTriggerHandler would simply invoke the appropriate event method for each trigger action in use.
Overall, the framework is fully featured, easy to manage, and creating new trigger actions is easy too. This is a good fit for a managed package or a larger org with many customizations. However, one should avoid using the Flow Trigger Actions because it won’t scale and the platform’s record-triggered flows are better managed and scale better compared to this. This means this framework should only be used for Apex trigger actions.
There are many Salesforce trigger frameworks available. My Simple Trigger Framework is a decent one to start with and it links to others.
Another thought just popped into my head… From a technical perspective, one should remember that “Programming To an Interface” is a good thing but that Interface can be an interface, virtual class or abstract class!