Advanced 30 min read

Domain Layer and Trigger Handlers Revisited

By the end of this lesson, you'll be able to:

  • Write a trigger handler class that centralizes an object's trigger dispatch logic
  • Route Module 42's two separate Transaction__c triggers through a single handler
  • Explain why a single handler per object is easier to reason about than several separate trigger files

Prerequisites: "Selector Layer"

The problem with two separate triggers on one object

Module 42 had both TransactionTrigger (after insert, balance updates) and TransactionValidation (before insert, validation) on the same Transaction__c object. This worked, but as an object accumulates more trigger logic over time, having several independent trigger files for the same object makes it hard to see, at a glance, everything that happens when a Transaction__c is inserted.

One handler, one entry point per event

public with sharing class TransactionTriggerHandler {
    public void beforeInsert(List<Transaction__c> newTransactions) {
        Set<Id> accountIds = new Set<Id>();
        for (Transaction__c t : newTransactions) accountIds.add(t.Bank_Account__c);

        Map<Id, Bank_Account__c> accounts = new BankAccountSelector().selectByIdAsMap(accountIds);
        TransactionValidationService.validate(newTransactions, accounts);
    }

    public void afterInsert(List<Transaction__c> newTransactions) {
        TransactionBalanceService.applyDeltas(newTransactions);
    }
}
  1. public with sharing class TransactionTriggerHandler { — one class, every event for this object.
  2. public void beforeInsert(List<Transaction__c> newTransactions) { — one clearly named method per event.
  3. Set<Id> accountIds = new Set<Id>(); — collects the distinct account Ids.
  4. for (Transaction__c t : newTransactions) accountIds.add(t.Bank_Account__c); — trigger mechanics.
  5. (blank line)
  6. Map<Id, Bank_Account__c> accounts = new BankAccountSelector().selectByIdAsMap(accountIds); — Lesson 4's selector, not a hand-written query.
  7. TransactionValidationService.validate(newTransactions, accounts); — Lesson 3's service, called from here.
  8. } — closes beforeInsert.
  9. (blank line)
  10. public void afterInsert(List<Transaction__c> newTransactions) { — the second event's method.
  11. TransactionBalanceService.applyDeltas(newTransactions); — the balance-update logic, similarly extracted.
  12. } — closes afterInsert.
  13. } — closes the class.
trigger TransactionTrigger on Transaction__c (before insert, after insert) {
    TransactionTriggerHandler handler = new TransactionTriggerHandler();
    if (Trigger.isBefore && Trigger.isInsert) handler.beforeInsert(Trigger.new);
    if (Trigger.isAfter && Trigger.isInsert) handler.afterInsert(Trigger.new);
}
  1. trigger TransactionTrigger on Transaction__c (before insert, after insert) { — one trigger file, both events.
  2. TransactionTriggerHandler handler = new TransactionTriggerHandler(); — one handler instance.
  3. if (Trigger.isBefore && Trigger.isInsert) handler.beforeInsert(Trigger.new); — routes the before-insert event.
  4. if (Trigger.isAfter && Trigger.isInsert) handler.afterInsert(Trigger.new); — routes the after-insert event.
  5. } — closes the trigger.

Now there is exactly one trigger file for Transaction__c, and exactly one handler class where every trigger event for that object is visible together — Lesson 3's TransactionValidationService and a new TransactionBalanceService (the balance-update logic from Module 42, similarly extracted) are both called from here.

Why one handler beats several independent trigger files

A developer asking "what happens when a Transaction__c is inserted?" now has exactly one file to open (TransactionTriggerHandler) rather than needing to know that logic is split across two separately-named trigger files that happen to both target the same object — a real cost that only shows up as an object accumulates more automation over time.

Exercise

As a comment, explain why the trigger itself (TransactionTrigger) contains almost no logic, delegating immediately to TransactionTriggerHandler.

Show hint

Think back to Lesson 2's separation-of-concerns framing of trigger mechanics as their own concern.

APEX

Domain Layer and Trigger Handlers Revisited Quiz

1. What is the main benefit of a single trigger handler class per object, compared to Module 42's two separate trigger files on Transaction__c?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A domain (trigger handler) class centralizes an object's trigger dispatch logic into one place — one class per object, with each trigger event routed to a clearly named method, rather than Module 42's two entirely separate trigger files for the same object.