Getting Started with the Trigger Framework - j-fischer/rflib GitHub Wiki

What is the RFLIB Trigger Framework?

The RFLIB Trigger Framework provides a metadata-driven approach to managing Salesforce triggers, offering:

  • Decoupled design: Separate trigger logic from trigger declarations
  • Configurable activation: Enable/disable handlers without code changes
  • Error handling: Built-in logging and exception management
  • Recursion tracking: Prevent infinite trigger loops
  • Order control: Define execution sequence for multiple handlers

Prerequisites

  1. RFLIB installed in your org
  2. Understanding of Salesforce triggers and governor limits
  3. Custom Metadata configuration access
  4. Basic Apex development knowledge

Architecture Overview

The framework follows this pattern:

  1. Trigger → calls rflib_TriggerManager.dispatch()
  2. TriggerManager → reads Custom Metadata configuration
  3. Configuration → defines which handlers to execute
  4. Handlers → contain your business logic

Setup Guide

Step 1: Create Your Handler Class

Create a new Apex class that implements the rflib_TriggerHandler interface:

public class AccountValidationHandler implements rflib_TriggerHandler {
    
    private static final rflib_Logger LOGGER = rflib_LoggerUtil.getFactory().createLogger('AccountValidationHandler');
    
    public void run(rflib_TriggerManager.Args args) {
        LOGGER.info('AccountValidationHandler.run() invoked for {0} event with {1} records', 
                   args.operation, args.newRecords.size());
        
        if (args.operation == TriggerOperation.BEFORE_INSERT || 
            args.operation == TriggerOperation.BEFORE_UPDATE) {
            validateAccounts(args.newRecords);
        }
    }
    
    public void onConsecutiveRun(rflib_TriggerManager.Args args, Integer numInvocation) {
        LOGGER.warn('AccountValidationHandler recursive execution #{0} detected', numInvocation);
        // Handle recursive scenario if needed
    }
    
    private void validateAccounts(List<Account> accounts) {
        // Your validation logic here
        for (Account acc : accounts) {
            if (String.isBlank(acc.Phone)) {
                acc.addError('Phone number is required');
            }
        }
    }
}

Step 2: Create the Trigger

Create a single trigger for all events (recommended approach):

trigger AccountTrigger on Account (before insert, after insert, before update, after update, before delete, after delete) {
    rflib_TriggerManager.dispatch(Account.SObjectType);
}

⚠️ Important: Create the trigger for all events even if you don't need them all initially. This prevents future deployments when adding new handler events.

Step 3: Configure the Custom Metadata

Create a Trigger Configuration record:

Trigger configuration custom metadata record

Required Fields:

  • Label: Descriptive name (e.g., "Account Validation Handler")
  • Object_Name__c: Account
  • Event__c: BEFORE_INSERT (or your desired trigger event)
  • Class_Name__c: AccountValidationHandler
  • Active__c: true
  • Order__c: 1 (execution order if multiple handlers exist)

Advanced Configuration

Multiple Handlers Example

You can have multiple handlers for the same object and event:

Handler 1: Account Validation (Order: 1)
Handler 2: Account Enrichment (Order: 2)  
Handler 3: Account Integration (Order: 3)

Feature Switch Integration

The framework automatically respects these feature switches:

  • rflib_Disable_All_Triggers: Disable switch
// This check is built into the framework
if (rflib_FeatureSwitch.isTurnedOff('rflib_Disable_All_Triggers')) {
    LOGGER.warn('All triggers disabled via feature switch');
    return;
}

Disable all triggers feature switch configuration


Testing Your Trigger Framework

Unit Test Template

Here's a test template for your trigger framework:

@IsTest
@SuppressWarnings('PMD.ClassNamingConventions')
private class AccountTriggerTest {

    private static final String AFTER_INSERT = TriggerOperation.AFTER_INSERT.name();
    
    @TestSetup
    static void makeData(){
        rflib_TestUtil.prepareLoggerForUnitTests();
    }

    @IsTest
    public static void testTriggerExecution() {
        // Mock the trigger configuration
        rflib_TriggerManager.QUERY_LOCATOR = new rflib_MockTriggerConfigQueryLocator(
            createConfiguration(), 
            Account.SObjectType.getDescribe().getName(), 
            AFTER_INSERT
        );
        
        Test.startTest();
        insert new Account(Name = 'Test Company');
        Test.stopTest();

        // Verify the handler was invoked
        System.assertEquals(1, rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS.size());

        rflib_TriggerManager.Args args = rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS.get(0);
        Account capturedRecord = (Account) args.newRecords.get(0); 
        System.assertEquals('Test Company', capturedRecord.Name);
    }

    private static rflib_Trigger_Configuration__mdt createConfiguration() {
        return new rflib_Trigger_Configuration__mdt(
            Active__c = true,
            Class_Name__c = 'rflib_MockTriggerHandler',
            Object_Name__c = 'Account',
            Event__c = AFTER_INSERT,
            Order__c = 1
        );
    }
}

Key Points:

  • Use rflib_TestUtil.prepareLoggerForUnitTests() in test setup
  • Mock the configuration using rflib_MockTriggerConfigQueryLocator
  • Verify handler execution with rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS

Built-in Feature Switches

The Trigger Framework automatically supports these feature switches:

Switch Name Description Use Case
rflib_Disable_All_Triggers Disables all trigger execution Emergency circuit breaker

Turning off all triggers can be useful when dealing with large data operations using Dataloader or ETL tools.


Troubleshooting & Best Practices

Common Issues

Issue 1: "Handler not executing"

Troubleshooting Checklist:

  1. ✅ Custom Metadata record exists and is Active
  2. Object_Name__c matches exactly (Account, not account)
  3. Event__c matches the trigger event (BEFORE_INSERT)
  4. Class_Name__c matches your handler class name exactly
  5. ✅ Handler class implements rflib_TriggerHandler interface
  6. ✅ No feature switches disabling triggers

Issue 2: "Recursive trigger detected"

Solution: The framework tracks recursion automatically. Implement logic in onConsecutiveRun():

public void onConsecutiveRun(rflib_TriggerManager.Args args, Integer numInvocation) {
    if (numInvocation > 2) {
        LOGGER.error('Too many recursive calls, skipping execution');
        return;
    }
    // Handle controlled recursion
}

Issue 3: "Multiple handlers not executing in order"

Solution: Check the Order__c field in your configurations:

Handler A: Order = 1 (executes first)
Handler B: Order = 2 (executes second)
Handler C: Order = 3 (executes third)

Best Practices

DO:

  • Create one trigger per object with all events
  • Use descriptive handler class names
  • Implement proper logging in handlers
  • Use the Order__c field for execution sequence
  • Test trigger with the provided test template
  • Handle bulk operations (200 records)

DON'T:

  • Create multiple triggers per object
  • Put business logic directly in triggers
  • Forget to activate your Custom Metadata records
  • Hard-code values in handlers

Performance Considerations

  • Bulkification: Always handle collections, not single records
  • SOQL Optimization: Use selective queries within handlers
  • Governor Limits: Monitor DML and SOQL usage across all handlers
  • Async Processing: Consider Retryable Actions for heavy operations

Next Steps

  1. Start Simple: Create one handler for one object and event
  2. Test Thoroughly: Use the provided test template
  3. Monitor Performance: Use the Ops Center to watch for issues
  4. Scale Gradually: Add more handlers as needed

Related Topics:

⚠️ **GitHub.com Fallback** ⚠️