Trigger Framework
A personal, lightweight Apex trigger framework for Salesforce
This trigger framework provides a structured approach to handling triggers in Salesforce, promoting clean code, maintainability, and scalability. The framework follows industry best practices and design patterns to ensure your trigger logic is organized and efficient.
- • Clean Architecture - Separation of concerns with handler and helper classes
- • Best Practices - Follows Salesforce recommended patterns and guidelines
- • Lightweight - Minimal overhead with maximum functionality
- • Maintainable - Easy to understand and modify code structure
- • Scalable - Designed to handle complex business logic efficiently
- • Well Documented - Comprehensive documentation and examples
TriggerHandler
Main entry point that determines which operations to perform based on trigger context (before/after, insert/update/delete).
TriggerHelper
Contains the actual business logic and processing methods. Separated for better testability and reusability.
cd sf-toolbox_trigger-framework
sf project deploy start --source-dir force-app
BaseTriggerHandler
Abstract base class that provides the framework structure and common functionality.
public void run() {
if (Trigger.isBefore) {
before();
} else if (Trigger.isAfter) {
after();
}
}
}
ITriggerHandler Interface
Defines the contract for all trigger handlers in the framework.
void run();
void before();
void after();
}
TriggerHelper Classes
Helper classes that contain the actual business logic and processing methods.
public static void handleBeforeInsert(List<Account> accounts) {
// Business logic here
}
public static void handleAfterInsert(List<Account> accounts) {
// Business logic here
}
}
1. Create a Trigger Handler
public class AccountTriggerHandler extends BaseTriggerHandler { public override void beforeInsert() { try { AccountTriggerHelper.handleBeforeInsert(Trigger.new); } catch (Exception ex) { System.debug(LoggingLevel.ERROR, 'Error in AccountTriggerHandler.beforeInsert: ' + ex.getMessage()); } } public override void afterInsert() { try { AccountTriggerHelper.handleAfterInsert(Trigger.new); } catch (Exception ex) { System.debug(LoggingLevel.ERROR, 'Error in AccountTriggerHandler.afterInsert: ' + ex.getMessage()); } } public override void beforeUpdate() { try { AccountTriggerHelper.handleBeforeUpdate(Trigger.new, Trigger.oldMap); } catch (Exception ex) { System.debug(LoggingLevel.ERROR, 'Error in AccountTriggerHandler.beforeUpdate: ' + ex.getMessage()); } } public override void afterUpdate() { try { AccountTriggerHelper.handleAfterUpdate(Trigger.new, Trigger.oldMap); } catch (Exception ex) { System.debug(LoggingLevel.ERROR, 'Error in AccountTriggerHandler.afterUpdate: ' + ex.getMessage()); } } }
2. Create a Trigger Helper
public class AccountTriggerHelper { public static void handleBeforeInsert(List<Account> accounts) { // Set default values, validate data, etc. for (Account acc : accounts) { if (String.isBlank(acc.Industry)) { acc.Industry = 'Technology'; } } } public static void handleAfterInsert(List<Account> accounts) { // Create related records, send notifications, etc. List<Contact> contactsToCreate = new List<Contact>(); for (Account acc : accounts) { if (acc.Create_Default_Contact__c) { contactsToCreate.add(new Contact( FirstName = 'Default', LastName = 'Contact', AccountId = acc.Id )); } } if (!contactsToCreate.isEmpty()) { insert contactsToCreate; } } public static void handleBeforeUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) { // Compare old vs new values, update fields, etc. for (Account acc : newAccounts) { Account oldAcc = oldAccounts.get(acc.Id); if (acc.Name != oldAcc.Name) { acc.Name_Changed__c = true; } } } public static void handleAfterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) { // Process changes, update related records, etc. // Implementation here } }
3. Create the Trigger
trigger AccountTrigger on Account (before insert, after insert, before update, after update) { AccountTriggerHandler handler = new AccountTriggerHandler(); handler.run(); }
- • One Trigger Per Object: Use only one trigger per object to avoid execution order issues
- • Bulk Processing: Always write triggers to handle bulk operations efficiently
- • Error Handling: Include comprehensive error handling in all trigger methods
- • Testing: Write thorough test classes with high code coverage
- • Separation of Concerns: Keep business logic in helper classes, not in triggers
- • Governor Limits: Be mindful of governor limits and optimize queries
- • Recursive Triggers: Implement mechanisms to prevent infinite recursion
- • Documentation: Document complex business logic and decision points
The framework includes comprehensive test coverage. When writing tests for trigger helpers, use DML operations rather than calling helper methods directly to ensure the trigger handler and helper methods fire properly.
@isTest public class AccountTriggerHandlerTest { @TestSetup static void setupTestData() { // Create test data using TestDataFactory List<Account> testAccounts = TestDataFactory.createAccounts(5); insert testAccounts; } @isTest static void testBeforeInsert() { // Test data setup List<Account> accounts = new List<Account>(); for (Integer i = 0; i < 3; i++) { accounts.add(new Account(Name = 'Test Account ' + i)); } Test.startTest(); insert accounts; // This will fire the trigger Test.stopTest(); // Assertions List<Account> insertedAccounts = [SELECT Industry FROM Account WHERE Name LIKE 'Test Account%']; for (Account acc : insertedAccounts) { System.assertEquals('Technology', acc.Industry, 'Default industry should be set'); } } }