Apex Design Patterns: Service, Domain and Selector Layers Explained

Header image: Apex Design Patterns, nested layers from callers such as UI, API and batch, through the Service layer and Domain layer to the Selector layer.

Hey friends,

The best-known Apex design patterns are the Apex Enterprise Patterns: a Service layer for business processes, a Domain layer for each object's validation and calculations, and a Selector layer that owns the SOQL, with triggers kept thin and routed to the Domain class. They all come from one idea, separation of concerns (SOC).

I'm sticking to what Salesforce teaches in the Trailhead modules Apex Enterprise Patterns: Service Layer and Domain and Selector Layers. The Platform Developer II exam asks for maintainable, reusable code that follows design patterns, and its Performance section asks when and how to reuse code.

Table of Apex enterprise pattern layers: Service owns cross-object business processes, Domain owns one object's validation and calculations, Selector owns SOQL queries, and the trigger only routes to the Domain class.

If you can name the layer a line of code belongs to, you're most of the way there.

What is the Service layer?

Trailhead quotes Martin Fowler's definition: it "defines an application's boundary with a layer of services." Its clients are other code: Lightning controllers, @AuraEnabled methods, Batch Apex, APIs. The design rules Trailhead gives:

  • Name methods for the business task, like InvoiceService.calculateTax(...), not for one caller, like handleTaxCodeForACME(...).
  • Bulkify signatures. Take lists, so callers don't loop over single calls.
  • Run with user security. Use with sharing on service classes, and elevate only briefly with a private without sharing inner class.
  • Leave error display to callers. Throw exceptions and let the UI, batch job or API decide how to show them.
  • Offer compound services so callers don't chain many calls in one transaction.
OpportunitiesService.cls
1public with sharing class OpportunitiesService {
2    public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {
3        if (discountPercentage < 0 || discountPercentage > 100) {
4            throw new OpportunitiesServiceException('Discount must be 0 to 100');
5        }
6        Opportunities opps = new Opportunities(
7            new OpportunitiesSelector().selectByIdWithLineItems(opportunityIds));
8        opps.applyDiscount(discountPercentage);
9    }
10    public class OpportunitiesServiceException extends Exception {}
11}

What is the Domain layer?

One Domain class per object (Trailhead names them in the plural, like Opportunities) holds that object's behavior: complex validation, defaulting and calculations. Both the Service layer and the trigger call into it, so the same rules apply whether a record is saved from the UI, an API or a batch job.

What is the Selector layer?

A Selector class holds the queries for an object. Trailhead's reasons: the same query copied around drifts over time, and code receiving records can't be sure which fields were queried. A selector gives every caller (Domain, Service, controllers, Batch Apex) the same fields and criteria.

Cert Lab by Nick Frates

Let the Platform Developer II Cert Lab test you to success

Questions on trigger design, code reuse, sharing keywords and refactoring sample code, with every option explained.

Start the Platform Developer II Cert Lab for $9.99 →

Not ready to buy? Try a free lesson and see how fast you learn.

What does a thin trigger look like?

Trailhead says triggers "make poor places for even moderately complex logic." The trigger only routes events. Trailhead's version uses the open source fflib library's fflib_SObjectDomain.triggerHandler. Without a library, a plain handler does the same job:

OpportunitiesTrigger.trigger
1trigger OpportunitiesTrigger on Opportunity (before insert, before update, after update) {
2    OpportunityTriggerHandler.run(Trigger.operationType, Trigger.new, Trigger.oldMap);
3}

One trigger per object, logic in a class, bulkified. My Apex trigger best practices post covers recursion guards and the order of execution.

Which other patterns should Platform Developer II candidates know?

PatternWhat it solvesWhere it shows up
Trigger handlerLogic out of triggers, one trigger per objectProcess Automation, Logic, and Integration
Unit of WorkCollect DML and commit once, in orderTrailhead's service example calls uow.commitWork()
Stub API mocksTest a class in isolation with System.StubProvider and Test.createStub()Testing, Debugging, and Deployment
Custom metadata for configChange behavior without code changesAdvanced Developer Fundamentals

The Stub API page in the Apex Developer Guide is the official source for the mocking row.

What design pattern mistakes should you avoid?

  • Logic in the trigger body. It can't be reused or unit tested on its own.
  • Caller-specific service methods. They multiply and drift apart.
  • Single-record signatures. They force loops that burn SOQL and DML limits.
  • SOQL scattered everywhere. Put it in a selector so fields and filters stay consistent.
  • Adopting every layer on day one. For a small org, a trigger handler and a selector or two may be plenty. Add layers when complexity earns them.

A bit about how I study. Focus on Force was my favorite resource on the way to 17 Salesforce certs. I started the traditional way, reading the study guide first and testing second, but rapid-fire practice tests are what actually made things stick. That's the loop Cert Lab is built on: quick questions, then explanations with current links, then go again.

FAQ

What are Apex Enterprise Patterns?

A set of layering patterns Salesforce teaches on Trailhead: Service, Domain and Selector layers, built on separation of concerns and adapted from Martin Fowler's enterprise application patterns.

Is fflib required?

No. Trailhead's examples use the open source fflib library, but the layering works with your own simple classes.

Where does trigger logic go?

In the Domain layer, called from a thin trigger. Trailhead notes triggers are missing from the Service layer's clients for that reason.

Are design patterns on the Platform Developer II exam?

Yes. The exam guide expects maintainable, reusable code following design patterns, and asks about trigger best practices and code reuse.

Where should you go next?

Nick Frates, a senior Salesforce developer with 17 certifications, built the Platform Developer II Cert Lab at nickfrates.com: rapid-fire practice questions for every exam section, each answer explained.

Cert Lab by Nick Frates

Let the Platform Developer II Cert Lab test you to success

Lessons, flashcards, a quiz for each of the five exam sections and full practice exams, every answer explained.

Start the Platform Developer II Cert Lab for $9.99 →

Not ready to buy? Try a free lesson and see how fast you learn.

Hope this helps!

Best,

Nick

Keep reading

See all posts →

Salesforce Saturdays

Join 1,033 Salesforce pros getting one 10-minute tip on Salesforce tech or career growth every Saturday.