Overview
The Apex Trigger Handler pattern is a structured approach for organizing Salesforce Apex triggers by delegating trigger logic to a handler class. It enforces separation of concerns, improves testability, ensures bulkification, and helps manage complex trigger behavior across different trigger contexts (before insert, after update, etc.). This pattern is a best practice for production-grade Salesforce development.
Why use the Trigger Handler pattern?
Triggers can quickly become hard to maintain when they contain business logic, SOQL queries, or DML operations directly. The Trigger Handler pattern addresses these problems by:
- Keeping trigger bodies thin and focused on context routing.
- Centralizing business logic in Apex classes that are easier to unit test.
- Encouraging single-responsibility and reuse across different triggers or services.
- Helping enforce bulk-safe design and governor limit considerations.
Core components of the pattern
The pattern typically consists of:
- One trigger per object — the trigger acts as a router for events.
- Trigger Handler class — a class that receives trigger context data and executes appropriate methods for each context.
- Domain / Service classes — optional classes that encapsulate business logic, validation, or DML operations.
- Context utility — helper code that simplifies checking trigger contexts and reduces repetitive code.
Typical Handler Anatomy
A simple, common structure looks like this:
// Trigger (AccountTrigger.trigger)
trigger AccountTrigger on Account (before insert, before update, after insert, after update, before delete, after delete, after undelete) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.new, Trigger.oldMap);
if (Trigger.isBefore) {
if (Trigger.isInsert) handler.beforeInsert();
if (Trigger.isUpdate) handler.beforeUpdate();
if (Trigger.isDelete) handler.beforeDelete();
}
if (Trigger.isAfter) {
if (Trigger.isInsert) handler.afterInsert();
if (Trigger.isUpdate) handler.afterUpdate();
if (Trigger.isDelete) handler.afterDelete();
if (Trigger.isUndelete) handler.afterUndelete();
}
}
// Handler (AccountTriggerHandler.cls)
public with sharing class AccountTriggerHandler {
private List
private Map
public AccountTriggerHandler(List
this.newList = newList;
this.oldMap = oldMap;
}
public void beforeInsert() {
// Validation, defaulting values — bulk-safe
}
public void beforeUpdate() {
// Compare oldMap and newList, prepare collections for DML
}
public void afterInsert() {
// Fire async processing, call services
}
public void afterUpdate() {
// Follow-up actions after commit
}
// ... other context methods
}
Best practices
- Implement a single trigger per sObject to avoid multiple triggers firing unpredictably.
- Keep trigger bodies minimal — only instantiate handler and route context.
- Ensure all handler methods are bulkified: operate on collections, not single records.
- Avoid DML and SOQL in loops; aggregate queries and DML outside per-record processing.
- Use @TestVisible and dependency injection where needed to facilitate unit testing and mocking.
- Protect against recursion by using static flags or a dedicated recursion guard utility.
- Prefer descriptive method names: beforeInsert, beforeUpdate, afterDelete, etc.
Common extensions
Advanced teams often build reusable trigger frameworks to reduce boilerplate. Typical extensions include:
- Base TriggerHandler abstract class with context dispatch logic.
- Declarative configuration for handler execution order and enabled features.
- Domain-driven design: separating validation, enrichment, and persistence layers.
- Asynchronous processing (Queueable/@Future/Platform Events) for heavy or external integrations.
Testing and governance
Unit tests should cover each trigger context and edge cases (bulk operations, mixed DML, nulls). Measure code coverage but also assert behavior: use small test data sets and bulk tests (200 records) to validate governor limits handling. When using async operations, use Test.startTest()/Test.stopTest() to assert job execution.
When not to use the pattern
For very small projects or simple automation where triggers are trivial, a handler may be overkill. However, for maintainability and scalability, adopting the pattern early saves technical debt later.
Summary
The Apex Trigger Handler pattern is a proven approach to keep triggers maintainable, testable, and bulk-safe. By routing trigger events to focused handler methods and using service/domain classes for business logic, teams can reduce bugs, manage governor limits, and scale Salesforce implementations more effectively.
Leave a Reply