Trigger Framework

A personal, lightweight Apex trigger framework for Salesforce

Apex Framework
Lightweight
Best Practices
Overview
A lightweight Apex trigger framework designed to streamline trigger management in Salesforce with clean architecture and best practices.

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
Architecture
The framework follows a handler-helper pattern that separates trigger logic from business logic.
Trigger → TriggerHandler → TriggerHelper → Business Logic

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.

Installation
Deploy the trigger framework to your Salesforce org
git clone https://github.com/anoble2020/sf-toolbox_trigger-framework.git
cd sf-toolbox_trigger-framework
sf project deploy start --source-dir force-app
Components
Key components of the Trigger Framework

BaseTriggerHandler

Abstract base class that provides the framework structure and common functionality.

public abstract class BaseTriggerHandler implements ITriggerHandler {
  public void run() {
    if (Trigger.isBefore) {
      before();
    } else if (Trigger.isAfter) {
      after();
    }
  }
}

ITriggerHandler Interface

Defines the contract for all trigger handlers in the framework.

public interface ITriggerHandler {
  void run();
  void before();
  void after();
}

TriggerHelper Classes

Helper classes that contain the actual business logic and processing methods.

public class AccountTriggerHelper {
  public static void handleBeforeInsert(List<Account> accounts) {
    // Business logic here
  }
  public static void handleAfterInsert(List<Account> accounts) {
    // Business logic here
  }
}
Implementation Example
How to create and use a trigger handler

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();
}
Best Practices
  • 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
Testing

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');
        }
    }
}