Advanced 35 min read

Build the Service Layer

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

  • Implement each health metric as its own focused method
  • Combine them into a single wrapper class for the dashboard
  • Keep the service layer entirely free of UI concerns

Prerequisites: "Design the Data Aggregation"

Each metric as its own method

public class AccountHealthService {
    public Integer getOpenCaseCount(Id accountId) {
        return [SELECT COUNT() FROM Case WHERE AccountId = :accountId AND IsClosed = false];
    }

    public Decimal getAverageResolutionDays(Id accountId) {
        List<Case> closedCases = [
            SELECT CreatedDate, ClosedDate FROM Case
            WHERE AccountId = :accountId AND IsClosed = true AND ClosedDate != null
        ];

        if (closedCases.isEmpty()) {
            return 0;
        }

        Integer totalDays = 0;
        for (Case c : closedCases) {
            totalDays += c.CreatedDate.date().daysBetween(c.ClosedDate.date());
        }
        return totalDays / (Decimal) closedCases.size();
    }
}

getOpenCaseCount uses Module 20's COUNT() directly; getAverageResolutionDays computes the average in Apex since it needs a date difference SOQL's AVG() alone can't express — both handle the empty-data case (Module 17's "Handle Edge Cases" habit) up front.

Combining metrics into one wrapper

public class AccountHealthSummary {
    public Integer openCaseCount;
    public Decimal averageResolutionDays;
    public Decimal totalWonRevenue;
    public Integer daysSinceLastActivity;
}

public AccountHealthSummary getHealthSummary(Id accountId) {
    AccountHealthSummary summary = new AccountHealthSummary();
    summary.openCaseCount = getOpenCaseCount(accountId);
    summary.averageResolutionDays = getAverageResolutionDays(accountId);
    summary.totalWonRevenue = getTotalWonRevenue(accountId);
    summary.daysSinceLastActivity = getDaysSinceLastActivity(accountId);
    return summary;
}

This is Module 34's DTO/wrapper-class pattern, used here at the service layer rather than directly in the controller — AccountHealthSummary doesn't yet have any @AuraEnabled annotations, since this class has no idea an LWC will ever use it (that's the controller's job, in a later lesson).

Why the service layer stays UI-agnostic

AccountHealthService and AccountHealthSummary could be called from an LWC controller, a batch job, or a plain Execute Anonymous script equally easily — nothing here mentions @AuraEnabled, cacheable, or anything LWC-specific. This mirrors Module 26's AccountRollupService: the logic that computes something and the logic that exposes it to a UI are deliberately kept as separate concerns.

Exercise

Add a getTotalWonRevenue(Id accountId) method that queries Total_Won_Revenue__c directly from the Account.

Show hint

[SELECT Total_Won_Revenue__c FROM Account WHERE Id = :accountId].Total_Won_Revenue__c

APEX

Build the Service Layer Quiz

1. Why does AccountHealthService have no @AuraEnabled annotations anywhere in it?

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 implements AccountHealthService — plain Apex, with no knowledge of LWC or @AuraEnabled at all, exactly the separation Module 34's controller lesson will build on top of.