This "framework" is an extension of the TriggerHandler pattern which original shipped with Mavensmate
The goal of this project is to improve upon that concept, by adding additional configuration and capabilities, while at the same time retaining full backwards compatibility.
- Dynamic Binding of Trigger Handlers via Custom Metadata (similar to table based trigger)
- Ability to disable whole triggers or individual handlers
- Easy to upgrade, full backwards compatibility
- Build in Error Handling & Logging
- Ability to decouple your handlers from the system static
Triggercontext, which allows for better unit testability (no need to actually run DML in tests)
- built in recursion control
- support for other popular framework patterns (sfdc-trigger-framework)
-
Package URL (replace host as needed)
-
command line:
sfdx force:package:install -p 04t1C000000lIX3QAM -u {TARGET ALIAS OR USERNAME} -b 1000 -w 1000
At it's core, the concept is very simple:
- Create a class that
implements YATF.Handler - Write a
void handle(){}method to perform the trigger logic - Create a trigger (one trigger per object!) and bind your handlers to the appropriate events.
global class AccountGreeter implements YATF.Handler{
global void handle(){
for(Account acc : (Account[]) Trigger.new){
System.debug('Hello ' + acc.Name);
}
}
}❓Why the 'global' access modifier❓
- In order to use the dynamic binding through custom metadata, you will need to set your access modifier for the handler classes to 'global'. This is because the trigger manager must actually create instances of your handler classes. Since this code lives inside of the YATF package, it will fail unless the class access is set to 'global'.
- The 'handle' method must be set to global for the same reason. The 'handle' method is being called within the manager code that lives in the YATF package, so without 'global' access the YATF package cannot see that method.
trigger AccountTrigger on Account(before insert){
YATF.Manager m = new YATF.Manager();
AccountGreeter greeter = new AccountGreeter();
m.bind(TriggerOperation.BEFORE_INSERT, greeter);
m.manage();
}Dynamic binding (or dependency injection) has the advantage of being able to switch out handlers without needed to deploy updates to the Trigger itself.
To do this for the above example:
- update the
.triggerto fire on all event:
trigger AccountMaster on Account(
after insert,
after update,
after delete,
after undelete,
before insert,
before update,
before delete
) {
new YATF.Manager().manage();
}WARNING: this is technically not required. The performance impact of binding to all events is untested. If you are concerned, only bind the events you are using!
-
Change the
Handleraccess modifier toglobal -
Setup the Custom Metadata
- navigate to
setup -> Custom Metadata -> Trigger Object -> manage - create a new Record.
- Set the
Object API Namefield to "Account" - Save
- Set the
- create a new child
Trigger Handler- Set the
Handler Classto "AccountGreeter" - Check the
Before Updatebox
- Set the
NOTES:
- Dynamic Binding and Static binding can co-exist! Statically bound handlers will execute first.
- You can still configuration for a statically bound trigger. The handler will only execute one time per event, but this allows you to disable a handler or use built in exception handling without having to deploy code.
You can disable the trigger on the entire SObject or just a single Handler. Simply un-check the Enabled field on the respective record.
NOTE: You can disable a static bound trigger by adding a Handler Configuration!
When a trigger throws an uncaught exception, you can choose how it should be handled via the On Exception field:
Throw: Just rethrows the error. Will cause the entire trigger to fail. This is the default.Rollback: Creates aSavepointprior to running handler and rolls back the transaction on exception. NOTE: this does NOT rollback changes made directly to the trigger context onBEFOREtriggers.Suppress: Will onlySystem.Debugthe exception.
Additionally, by setting Create_Exception_Event__c the system will platform events named Trigger_Handler_Exception__e any time an uncaught exception is thrown. This event contains details of the thrown exception, the handler that failed and the trigger context.
You can register a listener to automatically run when a Trigger_Handler_Exception__e occurs by setting the On_Exception_Event_Handler__c field. The value must be the name of a global class which implements ExceptionEventHandler.
You can optionally use On_Exception_Event_Handler_Props__c to pass json properties which will be used to initialize the class.
Sends an email with the details of the exception to one or more recipients. Requires the following On_Exception_Event_Handler_Props__c properties
fromEmailAddress: The ORG WIDE email address the email will be sent from
recipients: String[] of emails to send the exceptions to.
Example JSON:
{
"fromEmailAddress": "james.bond@callaway.cloud",
"recipients": ["john@example.com", "jane@example.com"]
}The design is not finalized, but the basic idea is to pass in or inject a proxy to the static Trigger variable (see TriggerContext). This will allow you to define your own context during unit tests.
-
Replace all instances of
TriggerHandler.HandlerInterfacewithYATF.Handler -
Replace all instances of
TriggerHandlerclass withYATF.Manager. -
Update all references of
TriggerHandler.Evtto eitherTriggerOperationorManager.EVT(easier to refactor but will be deprecated at some point). -
Delete the
TriggerHandlerclass from org
That's it! There should be no functional changes, but it's a good idea to run all unit tests before and after to confirm.
Please do! We will try to incorporate all reasonable ideas.
MIT




