Guides Salesforce Apex Enterprise Patterns (FFLIB) implementation: Selector, Domain, Service, Unit of Work layers for scalable apps beyond simple orgs.
npx claudepluginhub jiten-singh-shahi/salesforce-claude-code --plugin salesforce-claude-codeThis skill is limited to using the following tools:
Implementation guidance for Apex Enterprise Patterns (AEP / FFLIB). Covers the four-layer architecture, pragmatic adoption, and when NOT to use them. Constraint rules live in `sf-apex-constraints`.
Provides patterns for Salesforce platform development: Lightning Web Components (LWC), Apex triggers/classes, REST/Bulk APIs, Connected Apps, Salesforce DX with scratch orgs and 2GP.
Writes and debugs Apex code, builds Lightning Web Components, optimizes SOQL queries, implements triggers, batch jobs, platform events, and Salesforce integrations. Use for CRM workflows, governor limits, bulk processing, and Salesforce DX CI/CD.
Provides reference architecture for Salesforce integrations using jsforce, SFDX, and event-driven patterns in Node.js apps and Apex metadata projects. Use for designing sync logic, project layouts, or standards.
Share bugs, ideas, or general feedback.
Implementation guidance for Apex Enterprise Patterns (AEP / FFLIB). Covers the four-layer architecture, pragmatic adoption, and when NOT to use them. Constraint rules live in sf-apex-constraints.
Reference: @../_reference/ENTERPRISE_PATTERNS.md
The rule: introduce a layer when the absence of that layer is causing a real problem.
Trigger / Controller / API
|
Service Layer <- Transaction boundary, orchestration
|
Domain Layer <- Business rules on record collections
|
Selector Layer <- All SOQL queries
|
Unit of Work <- All DML (atomic commit)
Selectors own all SOQL queries for an object. No SOQL appears outside a Selector.
Naming: {ObjectNamePlural}Selector — e.g., AccountsSelector, OpportunitiesSelector
public with sharing class AccountsSelector {
@TestVisible
private static AccountsSelector instance;
public static AccountsSelector newInstance() {
if (instance == null) instance = new AccountsSelector();
return instance;
}
public List<Account> selectById(Set<Id> accountIds) {
return [
SELECT Id, Name, Type, OwnerId, AnnualRevenue,
Customer_Tier__c, CreditLimit__c
FROM Account WHERE Id IN :accountIds
WITH USER_MODE ORDER BY Name
];
}
public List<Account> selectWithOpenOpportunitiesById(Set<Id> accountIds) {
return [
SELECT Id, Name, AnnualRevenue, Customer_Tier__c,
(SELECT Id, Name, Amount, CloseDate, StageName
FROM Opportunities WHERE IsClosed = false
ORDER BY CloseDate ASC)
FROM Account WHERE Id IN :accountIds WITH USER_MODE
];
}
}
public with sharing class AccountsSelector extends fflib_SObjectSelector {
public static AccountsSelector newInstance() {
return (AccountsSelector) Application.Selector.newInstance(Account.SObjectType);
}
public Schema.SObjectType getSObjectType() { return Account.SObjectType; }
public List<Schema.SObjectField> getSObjectFieldList() {
return new List<Schema.SObjectField>{
Account.Id, Account.Name, Account.Type,
Account.OwnerId, Account.AnnualRevenue
};
}
public List<Account> selectById(Set<Id> accountIds) {
return (List<Account>) selectSObjectsById(accountIds);
}
}
Encapsulates all business logic for a collection of records of the same type. Replaces trigger logic.
Naming: {ObjectNamePlural} — e.g., Accounts, Opportunities
public with sharing class Accounts {
private final List<Account> records;
private final Map<Id, Account> existingRecords;
public static Accounts newInstance(List<Account> records) {
return new Accounts(records, null);
}
public static Accounts newInstance(List<Account> records, Map<Id, Account> existing) {
return new Accounts(records, existing);
}
private Accounts(List<Account> records, Map<Id, Account> existingRecords) {
this.records = records;
this.existingRecords = existingRecords;
}
public void onBeforeInsert() {
setDefaultCustomerTier();
validateRequiredFields();
}
public void onBeforeUpdate() {
validateRequiredFields();
preventDowngradingPremiumTier();
}
public void setDefaultCustomerTier() {
for (Account acc : records) {
if (String.isBlank(acc.Customer_Tier__c)) acc.Customer_Tier__c = 'Standard';
}
}
public void validateRequiredFields() {
for (Account acc : records) {
if (acc.Type == 'Customer' && String.isBlank(acc.Industry)) {
acc.Industry.addError('Industry is required for Customer account type.');
}
}
}
public void preventDowngradingPremiumTier() {
for (Account acc : records) {
Account existing = existingRecords?.get(acc.Id);
if (existing == null) continue;
if (existing.Customer_Tier__c == 'Premium'
&& acc.Customer_Tier__c != 'Premium') {
acc.Customer_Tier__c.addError(
'Premium tier downgrade requires approval.'
);
}
}
}
}
trigger AccountTrigger on Account (
before insert, before update, after insert, after update
) {
if (Trigger.isBefore && Trigger.isInsert) {
Accounts.newInstance(Trigger.new).onBeforeInsert();
} else if (Trigger.isBefore && Trigger.isUpdate) {
Accounts.newInstance(Trigger.new, Trigger.oldMap).onBeforeUpdate();
}
}
Orchestrates business processes that span multiple objects or require a full transaction boundary.
Naming: {ObjectNamePlural}Service — e.g., AccountsService
Rules:
public with sharing class AccountsService {
public static void upgradeToPremium(Set<Id> accountIds) {
List<Account> accounts = AccountsSelector.newInstance()
.selectWithOpenOpportunitiesById(accountIds);
if (accounts.isEmpty()) {
throw new UpgradeException('No accounts found for IDs: ' + accountIds);
}
// Validate
List<String> errors = validateForUpgrade(accounts);
if (!errors.isEmpty()) {
throw new UpgradeException(String.join(errors, '\n'));
}
// Build Unit of Work
fflib_ISObjectUnitOfWork uow = Application.UnitOfWork.newInstance();
for (Account acc : accounts) {
acc.Customer_Tier__c = 'Premium';
acc.CreditLimit__c = 100000.00;
uow.registerDirty(acc);
uow.registerNew(new Opportunity(
Name = acc.Name + ' - Premium Welcome',
AccountId = acc.Id,
StageName = 'Qualification',
CloseDate = Date.today().addDays(30)
));
}
uow.commitWork(); // One atomic DML transaction
}
public class UpgradeException extends Exception {}
}
Accumulates all DML operations and commits them in a single, ordered, atomic transaction.
public class SimpleUnitOfWork {
private List<SObject> toInsert = new List<SObject>();
private List<SObject> toUpdate = new List<SObject>();
private List<SObject> toDelete = new List<SObject>();
public void registerNew(SObject record) { toInsert.add(record); }
public void registerDirty(SObject record) { toUpdate.add(record); }
public void registerDeleted(SObject record) { toDelete.add(record); }
public void commitWork() {
Savepoint sp = Database.setSavepoint();
try {
if (!toInsert.isEmpty()) insert toInsert;
if (!toUpdate.isEmpty()) update toUpdate;
if (!toDelete.isEmpty()) delete toDelete;
} catch (Exception e) {
Database.rollback(sp);
throw e;
}
}
}
public class Application {
public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
new fflib_Application.UnitOfWorkFactory(
new List<SObjectType>{
Account.SObjectType,
Contact.SObjectType,
Opportunity.SObjectType
}
);
public static final fflib_Application.SelectorFactory Selector =
new fflib_Application.SelectorFactory(
new Map<SObjectType, Type>{
Account.SObjectType => AccountsSelector.class,
Opportunity.SObjectType => OpportunitiesSelector.class
}
);
}
Centralize SOQL into Selectors, business processes into Services. No FFLIB dependency needed.
When trigger logic grows beyond simple field defaults, introduce the Domain layer.
When a service needs to insert/update multiple related objects, introduce UoW for atomicity.
# Clone and deploy FFLIB
git clone https://github.com/apex-enterprise-patterns/fflib-apex-common.git
git clone https://github.com/apex-enterprise-patterns/fflib-apex-mocks.git
sf project deploy start --source-dir fflib-apex-common/sfdx-source --target-org my-org
sf project deploy start --source-dir fflib-apex-mocks/sfdx-source --target-org my-org
FFLIB is typically deployed as unmanaged source code directly from the cloned repositories, not as a versioned managed package.
sf-review-agent, sf-architect — For interactive guidancesf-apex-constraints — Governs all Apex code including enterprise pattern implementations