Without discipline, an Apex codebase devolves into giant classes with mixed concerns. Key patterns:
1. Single Responsibility Principle. Each class does one thing. AccountTriggerHandler handles trigger orchestration; AccountService handles business logic; AccountRepository handles SOQL/DML.
2. Layered architecture.
- Trigger layer — minimal; just delegates to handler.
- Handler layer — orchestrates the trigger context's events.
- Service layer — business rules. Stateless. Takes inputs, returns outputs.
- Repository / Selector layer — encapsulates SOQL/DML. Returns sObjects.
- Domain layer (optional) — wraps sObjects with domain logic (e.g.,
class AccountDomain { calculateTier(); }).
3. Dependency injection.
apex public class OrderService { private NotificationService notifier; public OrderService(NotificationService notifier) { this.notifier = notifier; } }
Inject collaborators rather than hard-coding new NotificationService(). Enables mocking in tests.
4. Interfaces for testability.
`apex public interface ICalloutService { HttpResponse send(HttpRequest req); }
public class HttpCalloutService implements ICalloutService { ... } public class MockCalloutService implements ICalloutService { ... } // for tests `
5. Selector pattern (FFLib). A class per object that owns SOQL.
apex public class AccountSelector { public List<Account> selectByIds(Set<Id> ids) { return [SELECT Id, Name, Phone FROM Account WHERE Id IN :ids]; } public List<Account> selectActiveByOwner(Id ownerId) { return [SELECT Id FROM Account WHERE OwnerId = :ownerId AND Active__c = true]; } }
Centralises queries; one place to update when schema changes.
6. Trigger handler framework. One trigger per object delegating to a handler class. Avoid logic in trigger files.
7. Custom exceptions for business errors.
apex public class AccountNotFoundException extends Exception {}
Clear semantics; catchable distinct from system exceptions.
8. Avoid static state. Static variables shared across requests cause unpredictable bugs in multi-threaded contexts (Apex is single-threaded but the principle holds).
9. Recognised frameworks:
- FFLib (Force-DI, Apex Common) — open-source patterns library that codifies the above.
- Apex Trigger Handler Framework by Kevin O'Hara — popular trigger framework.
10. Code reviews and conventions. Establish team conventions: naming, file structure, dependency direction. Lint with PMD.
A clean Apex codebase reads like layered application code, not glue scripts.
