Triggers & the handler pattern
A trigger is Apex that runs automatically when records are inserted, updated, deleted, or undeleted. Triggers are how developers extend the platform's save behaviour beyond what Flow can do. They are powerful and unavoidable, but they are also where undisciplined code causes the most damage, so the industry has converged on a clear pattern for writing them well.
Triggers fire in contexts: before and after, each for insert/update/delete/undelete. Use before to validate or set fields on the records being saved (no DML needed), and after when you need the record's Id or must touch related records. Trigger context variables — Trigger.new, Trigger.old, Trigger.newMap, Trigger.oldMap, and the booleans like Trigger.isBefore — give you the records and the situation.
The near-universal best practice is one trigger per object, containing no business logic itself, delegating immediately to a handler class. This keeps execution order predictable and logic testable.
// The trigger: thin, just routes to the handler
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
new AccountTriggerHandler().run();
}
// The handler: bulk-safe logic, one method per context
public class AccountTriggerHandler {
public void run() {
if (Trigger.isBefore && Trigger.isInsert) beforeInsert(Trigger.new);
if (Trigger.isAfter && Trigger.isUpdate) afterUpdate(Trigger.newMap, Trigger.oldMap);
}
private void beforeInsert(List<Account> newAccts) {
for (Account a : newAccts) {
if (String.isBlank(a.Rating)) a.Rating = 'Warm'; // set-based, no DML
}
}
private void afterUpdate(Map<Id,Account> newMap, Map<Id,Account> oldMap) {
// compare old vs new, collect related work, DML once
}
}
Three rules keep triggers healthy: bulkify everything (assume 200 records), never put SOQL or DML inside loops, and guard against recursion with a static flag when your logic might re-trigger itself. Keeping the trigger body to a single line makes it trivial to see and reason about the entry point.
As an exercise, build a one-line trigger and handler that defaults a field on insert and creates a related task on update, all bulk-safe. Then, crucially, cover it with tests — the subject of the next lesson.