Intermediate 30 min read

Add Service Classes

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

  • Extract the Account rollup logic into a dedicated service class
  • Explain why this separation helps beyond just the trigger context
  • Call the service class from the trigger handler

Prerequisites: "Handle Governor Limits"

Why go further than the handler class

Module 24's handler-class pattern already separates the trigger from its logic — but the rollup logic itself (applyAdjustments) is currently only reachable through OpportunityTriggerHandler. If a future data-migration script needed to recalculate every Account's Total_Won_Revenue__c from scratch, it couldn't reuse this logic without going through a fake trigger context.

Extracting an AccountRollupService

public class AccountRollupService {
    public void adjustWonRevenue(Map<Id, Decimal> amountAdjustmentByAccountId) {
        if (amountAdjustmentByAccountId.isEmpty()) {
            return;
        }

        List<Account> accounts = [
            SELECT Id, Total_Won_Revenue__c
            FROM Account
            WHERE Id IN :amountAdjustmentByAccountId.keySet()
        ];

        for (Account acc : accounts) {
            Decimal current = acc.Total_Won_Revenue__c == null ? 0 : acc.Total_Won_Revenue__c;
            acc.Total_Won_Revenue__c = current + amountAdjustmentByAccountId.get(acc.Id);
        }

        update accounts;
    }
}
  1. public class AccountRollupService { — a new, dedicated class.
  2. public void adjustWonRevenue(Map<Id, Decimal> amountAdjustmentByAccountId) { — a clear, reusable name.
  3. if (amountAdjustmentByAccountId.isEmpty()) { — the same early-exit as before.
  4. return; — nothing to do.
  5. } — closes the if.
  6. (blank line)
  7. List<Account> accounts = [ — the same query as applyAdjustments had.
  8. SELECT Id, Total_Won_Revenue__c — the fields needed.
  9. FROM Account — the object.
  10. WHERE Id IN :amountAdjustmentByAccountId.keySet() — the affected accounts.
  11. ]; — closes the query.
  12. (blank line)
  13. for (Account acc : accounts) { — loops over them.
  14. Decimal current = acc.Total_Won_Revenue__c == null ? 0 : acc.Total_Won_Revenue__c; — defaults null to 0.
  15. acc.Total_Won_Revenue__c = current + amountAdjustmentByAccountId.get(acc.Id); — applies the adjustment.
  16. } — closes the loop.
  17. (blank line)
  18. update accounts; — one bulk update.
  19. } — closes the method.
  20. } — closes the class — AccountRollupService.adjustWonRevenue() can be called from the trigger handler, a batch job, or a one-off Execute Anonymous script equally easily.

The trigger handler now delegates

public class OpportunityTriggerHandler {
    public void afterUpdate(List<Opportunity> newOpportunities, Map<Id, Opportunity> oldMap) {
        Map<Id, Decimal> amountAdjustmentByAccountId = new Map<Id, Decimal>();

        for (Opportunity opp : newOpportunities) {
            // ... build amountAdjustmentByAccountId exactly as before ...
        }

        new AccountRollupService().adjustWonRevenue(amountAdjustmentByAccountId);
    }
}
  1. public class OpportunityTriggerHandler { — now has one job — detecting which Opportunities changed and by how much.
  2. public void afterUpdate(List<Opportunity> newOpportunities, Map<Id, Opportunity> oldMap) { — the method.
  3. Map<Id, Decimal> amountAdjustmentByAccountId = new Map<Id, Decimal>(); — the same collection as before.
  4. (blank line)
  5. for (Opportunity opp : newOpportunities) { — the same detection logic.
  6. // ... build amountAdjustmentByAccountId exactly as before ... — unchanged from the earlier lesson.
  7. } — closes the loop.
  8. (blank line)
  9. new AccountRollupService().adjustWonRevenue(amountAdjustmentByAccountId); — delegates the actual work to a different, focused class.
  10. } — closes the method.
  11. } — closes the class.

This is Module 14's Single Responsibility Principle, now spanning two classes instead of living inside one growing handler.

Exercise

As a comment, name one other caller (besides OpportunityTriggerHandler) that could now reuse AccountRollupService.adjustWonRevenue() directly.

Show hint

Think about data cleanup, migrations, or scheduled recalculations.

APEX

Add Service Classes Quiz

1. What does extracting AccountRollupService add beyond Module 24's handler-class pattern alone?

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

This lesson extracts the rollup logic out of the trigger handler entirely, into an AccountRollupService — reusable from anywhere, not just this one trigger.