sf-agent-framework
Version:
AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction
798 lines (632 loc) • 22.8 kB
Markdown
# Salesforce Design Patterns
## Overview
Design patterns are reusable solutions to common problems in Salesforce
development. This guide covers enterprise patterns, best practices, and
implementation approaches.
## Apex Design Patterns
### Singleton Pattern
**Purpose**: Ensure only one instance of a class exists
**Use Case**: Managing shared resources, caching, configuration
**Implementation**:
```apex
public class ConfigurationManager {
private static ConfigurationManager instance;
private Map<String, Configuration_Setting__c> settings;
private ConfigurationManager() {
loadSettings();
}
public static ConfigurationManager getInstance() {
if(instance == null) {
instance = new ConfigurationManager();
}
return instance;
}
private void loadSettings() {
settings = new Map<String, Configuration_Setting__c>();
for(Configuration_Setting__c setting : [SELECT Name, Value__c, Type__c
FROM Configuration_Setting__c]) {
settings.put(setting.Name, setting);
}
}
public String getValue(String key) {
Configuration_Setting__c setting = settings.get(key);
return setting != null ? setting.Value__c : null;
}
}
```
### Factory Pattern
**Purpose**: Create objects without specifying exact classes
**Use Case**: Dynamic object creation, strategy selection
**Implementation**:
```apex
public interface PaymentProcessor {
PaymentResult processPayment(Decimal amount, String currency);
}
public class PaymentProcessorFactory {
public static PaymentProcessor getProcessor(String type) {
switch on type {
when 'CREDIT_CARD' {
return new CreditCardProcessor();
}
when 'PAYPAL' {
return new PayPalProcessor();
}
when 'BANK_TRANSFER' {
return new BankTransferProcessor();
}
when else {
throw new PaymentException('Unknown payment type: ' + type);
}
}
}
}
// Usage
PaymentProcessor processor = PaymentProcessorFactory.getProcessor(order.Payment_Type__c);
PaymentResult result = processor.processPayment(order.Total_Amount__c, order.Currency__c);
```
### Strategy Pattern
**Purpose**: Define family of algorithms, make them interchangeable
**Use Case**: Discount calculations, tax computation, shipping methods
**Implementation**:
```apex
public interface DiscountStrategy {
Decimal calculateDiscount(Order__c order);
}
public class VolumeDiscountStrategy implements DiscountStrategy {
public Decimal calculateDiscount(Order__c order) {
if(order.Quantity__c >= 100) return order.Total__c * 0.15;
if(order.Quantity__c >= 50) return order.Total__c * 0.10;
if(order.Quantity__c >= 20) return order.Total__c * 0.05;
return 0;
}
}
public class LoyaltyDiscountStrategy implements DiscountStrategy {
public Decimal calculateDiscount(Order__c order) {
Account customer = [SELECT Loyalty_Tier__c FROM Account WHERE Id = :order.Account__c];
switch on customer.Loyalty_Tier__c {
when 'PLATINUM' { return order.Total__c * 0.20; }
when 'GOLD' { return order.Total__c * 0.15; }
when 'SILVER' { return order.Total__c * 0.10; }
when else { return 0; }
}
}
}
public class DiscountCalculator {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public Decimal calculateDiscount(Order__c order) {
return strategy.calculateDiscount(order);
}
}
```
### Builder Pattern
**Purpose**: Construct complex objects step by step
**Use Case**: Creating test data, complex object initialization
**Implementation**:
```apex
public class AccountBuilder {
private Account account;
public AccountBuilder() {
this.account = new Account();
}
public AccountBuilder withName(String name) {
this.account.Name = name;
return this;
}
public AccountBuilder withIndustry(String industry) {
this.account.Industry = industry;
return this;
}
public AccountBuilder withAnnualRevenue(Decimal revenue) {
this.account.AnnualRevenue = revenue;
return this;
}
public AccountBuilder withBillingAddress(String street, String city, String state, String postalCode) {
this.account.BillingStreet = street;
this.account.BillingCity = city;
this.account.BillingState = state;
this.account.BillingPostalCode = postalCode;
return this;
}
public AccountBuilder asCustomer() {
this.account.Type = 'Customer';
return this;
}
public Account build() {
// Validate required fields
if(String.isBlank(account.Name)) {
throw new BuilderException('Account name is required');
}
return account;
}
public Account buildAndInsert() {
Account acc = build();
insert acc;
return acc;
}
}
// Usage
Account testAccount = new AccountBuilder()
.withName('Acme Corporation')
.withIndustry('Technology')
.withAnnualRevenue(1000000)
.asCustomer()
.buildAndInsert();
```
### Repository Pattern
**Purpose**: Encapsulate data access logic
**Use Case**: Complex queries, data access abstraction
**Implementation**:
```apex
public interface AccountRepository {
Account findById(Id accountId);
List<Account> findByIndustry(String industry);
List<Account> findCustomersWithMinRevenue(Decimal minRevenue);
void save(Account account);
void save(List<Account> accounts);
}
public class AccountRepositoryImpl implements AccountRepository {
public Account findById(Id accountId) {
List<Account> accounts = [SELECT Id, Name, Industry, Type, AnnualRevenue,
BillingStreet, BillingCity, BillingState
FROM Account
WHERE Id = :accountId
WITH SECURITY_ENFORCED];
return accounts.isEmpty() ? null : accounts[0];
}
public List<Account> findByIndustry(String industry) {
return [SELECT Id, Name, Industry, Type, AnnualRevenue
FROM Account
WHERE Industry = :industry
WITH SECURITY_ENFORCED];
}
public List<Account> findCustomersWithMinRevenue(Decimal minRevenue) {
return [SELECT Id, Name, AnnualRevenue
FROM Account
WHERE Type = 'Customer'
AND AnnualRevenue >= :minRevenue
WITH SECURITY_ENFORCED
ORDER BY AnnualRevenue DESC];
}
public void save(Account account) {
upsert account;
}
public void save(List<Account> accounts) {
upsert accounts;
}
}
```
### Service Layer Pattern
**Purpose**: Encapsulate business logic
**Use Case**: Complex business operations, transaction management
**Implementation**:
```apex
public class OrderService {
private AccountRepository accountRepo;
private OrderRepository orderRepo;
private InventoryService inventoryService;
private PricingService pricingService;
public OrderService() {
this.accountRepo = new AccountRepositoryImpl();
this.orderRepo = new OrderRepositoryImpl();
this.inventoryService = new InventoryService();
this.pricingService = new PricingService();
}
public OrderResult createOrder(OrderRequest request) {
Savepoint sp = Database.setSavepoint();
try {
// Validate customer
Account customer = accountRepo.findById(request.accountId);
if(customer == null) {
throw new OrderException('Invalid customer');
}
// Check inventory
Boolean available = inventoryService.checkAvailability(
request.productIds,
request.quantities
);
if(!available) {
throw new OrderException('Insufficient inventory');
}
// Calculate pricing
PricingResult pricing = pricingService.calculatePricing(
customer,
request.productIds,
request.quantities
);
// Create order
Order__c order = new Order__c(
Account__c = customer.Id,
Status__c = 'Draft',
Total_Amount__c = pricing.totalAmount,
Discount_Amount__c = pricing.discountAmount
);
orderRepo.save(order);
// Create order lines
List<Order_Line__c> lines = createOrderLines(
order.Id,
request,
pricing
);
insert lines;
// Reserve inventory
inventoryService.reserveInventory(
order.Id,
request.productIds,
request.quantities
);
return new OrderResult(order.Id, true, 'Order created successfully');
} catch(Exception e) {
Database.rollback(sp);
return new OrderResult(null, false, e.getMessage());
}
}
}
```
### Unit of Work Pattern
**Purpose**: Maintain list of objects affected by business transaction
**Use Case**: Complex DML operations, transaction management
**Implementation**:
```apex
public class UnitOfWork {
private Map<Schema.SObjectType, List<SObject>> newRecords;
private Map<Schema.SObjectType, List<SObject>> dirtyRecords;
private Map<Schema.SObjectType, List<SObject>> deletedRecords;
private List<Schema.SObjectType> dmlOrder;
public UnitOfWork(List<Schema.SObjectType> dmlOrder) {
this.dmlOrder = dmlOrder;
this.newRecords = new Map<Schema.SObjectType, List<SObject>>();
this.dirtyRecords = new Map<Schema.SObjectType, List<SObject>>();
this.deletedRecords = new Map<Schema.SObjectType, List<SObject>>();
}
public void registerNew(SObject record) {
Schema.SObjectType sObjectType = record.getSObjectType();
if(!newRecords.containsKey(sObjectType)) {
newRecords.put(sObjectType, new List<SObject>());
}
newRecords.get(sObjectType).add(record);
}
public void registerDirty(SObject record) {
Schema.SObjectType sObjectType = record.getSObjectType();
if(!dirtyRecords.containsKey(sObjectType)) {
dirtyRecords.put(sObjectType, new List<SObject>());
}
dirtyRecords.get(sObjectType).add(record);
}
public void registerDeleted(SObject record) {
Schema.SObjectType sObjectType = record.getSObjectType();
if(!deletedRecords.containsKey(sObjectType)) {
deletedRecords.put(sObjectType, new List<SObject>());
}
deletedRecords.get(sObjectType).add(record);
}
public void commitWork() {
Savepoint sp = Database.setSavepoint();
try {
// Insert new records
for(Schema.SObjectType sObjectType : dmlOrder) {
if(newRecords.containsKey(sObjectType)) {
insert newRecords.get(sObjectType);
}
}
// Update dirty records
for(Schema.SObjectType sObjectType : dmlOrder) {
if(dirtyRecords.containsKey(sObjectType)) {
update dirtyRecords.get(sObjectType);
}
}
// Delete removed records
for(Schema.SObjectType sObjectType : dmlOrder) {
if(deletedRecords.containsKey(sObjectType)) {
delete deletedRecords.get(sObjectType);
}
}
} catch(Exception e) {
Database.rollback(sp);
throw e;
}
}
}
```
## Integration Patterns
### Remote Proxy Pattern
**Purpose**: Provide local representation of remote service
**Use Case**: External API integration
**Implementation**:
```apex
public class WeatherServiceProxy {
private String apiKey;
private String endpoint;
public WeatherServiceProxy() {
this.apiKey = ConfigurationManager.getInstance().getValue('WEATHER_API_KEY');
this.endpoint = 'callout:Weather_Service';
}
public WeatherData getWeather(String city) {
// Check cache first
String cacheKey = 'weather_' + city;
WeatherData cached = (WeatherData)Cache.Org.get(cacheKey);
if(cached != null) {
return cached;
}
// Make callout
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint + '/weather?city=' + EncodingUtil.urlEncode(city, 'UTF-8'));
request.setMethod('GET');
request.setHeader('X-API-Key', apiKey);
request.setTimeout(30000);
HttpResponse response = http.send(request);
if(response.getStatusCode() == 200) {
WeatherData data = (WeatherData)JSON.deserialize(
response.getBody(),
WeatherData.class
);
// Cache for 1 hour
Cache.Org.put(cacheKey, data, 3600);
return data;
} else {
throw new IntegrationException('Weather service error: ' + response.getStatus());
}
}
}
```
### Circuit Breaker Pattern
**Purpose**: Prevent cascading failures in distributed systems
**Use Case**: Protecting against failing external services
**Implementation**:
```apex
public class CircuitBreaker {
private String serviceName;
private Integer failureThreshold;
private Integer successThreshold;
private Integer timeout;
private Integer failureCount = 0;
private Integer successCount = 0;
private DateTime lastFailureTime;
private State currentState = State.CLOSED;
public enum State {
CLOSED, // Normal operation
OPEN, // Failing, reject calls
HALF_OPEN // Testing if service recovered
}
public CircuitBreaker(String serviceName, Integer failureThreshold,
Integer successThreshold, Integer timeout) {
this.serviceName = serviceName;
this.failureThreshold = failureThreshold;
this.successThreshold = successThreshold;
this.timeout = timeout;
}
public Object callService(Callable service) {
if(currentState == State.OPEN) {
if(DateTime.now().getTime() - lastFailureTime.getTime() > timeout * 1000) {
currentState = State.HALF_OPEN;
successCount = 0;
} else {
throw new CircuitBreakerException('Circuit breaker is OPEN for ' + serviceName);
}
}
try {
Object result = service.call();
onSuccess();
return result;
} catch(Exception e) {
onFailure();
throw e;
}
}
private void onSuccess() {
failureCount = 0;
if(currentState == State.HALF_OPEN) {
successCount++;
if(successCount >= successThreshold) {
currentState = State.CLOSED;
}
}
}
private void onFailure() {
lastFailureTime = DateTime.now();
failureCount++;
if(failureCount >= failureThreshold) {
currentState = State.OPEN;
}
}
}
```
### Adapter Pattern
**Purpose**: Allow incompatible interfaces to work together
**Use Case**: Integrating different systems with varying formats
**Implementation**:
```apex
// Legacy system interface
public interface LegacyCustomerSystem {
String getCustomerData(String customerId);
}
// Modern interface expected by our system
public interface CustomerService {
Customer getCustomer(Id accountId);
}
// Adapter to bridge the gap
public class LegacyCustomerAdapter implements CustomerService {
private LegacyCustomerSystem legacySystem;
public LegacyCustomerAdapter(LegacyCustomerSystem legacySystem) {
this.legacySystem = legacySystem;
}
public Customer getCustomer(Id accountId) {
// Get legacy customer ID from Account
Account acc = [SELECT Legacy_Customer_ID__c FROM Account WHERE Id = :accountId];
if(String.isBlank(acc.Legacy_Customer_ID__c)) {
return null;
}
// Get data from legacy system
String xmlData = legacySystem.getCustomerData(acc.Legacy_Customer_ID__c);
// Parse XML and convert to our Customer format
Customer customer = new Customer();
Dom.Document doc = new Dom.Document();
doc.load(xmlData);
Dom.XmlNode root = doc.getRootElement();
customer.name = root.getChildElement('name', null).getText();
customer.email = root.getChildElement('email', null).getText();
customer.phone = root.getChildElement('phone', null).getText();
return customer;
}
}
```
## Trigger Patterns
### Trigger Handler Pattern
**Purpose**: Separate trigger logic from handler classes
**Use Case**: All trigger implementations
**Implementation**:
```apex
// Generic trigger handler
public virtual class TriggerHandler {
protected Boolean isDisabled = false;
public void run() {
if(isDisabled) return;
switch on Trigger.operationType {
when BEFORE_INSERT { beforeInsert(); }
when BEFORE_UPDATE { beforeUpdate(); }
when BEFORE_DELETE { beforeDelete(); }
when AFTER_INSERT { afterInsert(); }
when AFTER_UPDATE { afterUpdate(); }
when AFTER_DELETE { afterDelete(); }
when AFTER_UNDELETE { afterUndelete(); }
}
}
protected virtual void beforeInsert() {}
protected virtual void beforeUpdate() {}
protected virtual void beforeDelete() {}
protected virtual void afterInsert() {}
protected virtual void afterUpdate() {}
protected virtual void afterDelete() {}
protected virtual void afterUndelete() {}
public void disable() {
this.isDisabled = true;
}
public void enable() {
this.isDisabled = false;
}
}
// Specific implementation
public class AccountTriggerHandler extends TriggerHandler {
private List<Account> newRecords;
private List<Account> oldRecords;
private Map<Id, Account> newMap;
private Map<Id, Account> oldMap;
public AccountTriggerHandler() {
this.newRecords = (List<Account>) Trigger.new;
this.oldRecords = (List<Account>) Trigger.old;
this.newMap = (Map<Id, Account>) Trigger.newMap;
this.oldMap = (Map<Id, Account>) Trigger.oldMap;
}
protected override void beforeInsert() {
for(Account acc : newRecords) {
// Standardize account names
acc.Name = AccountService.standardizeName(acc.Name);
// Set defaults
if(acc.Industry == null) {
acc.Industry = 'Other';
}
}
}
protected override void afterUpdate() {
List<Account> accountsWithAddressChange = new List<Account>();
for(Account acc : newRecords) {
Account oldAcc = oldMap.get(acc.Id);
if(hasAddressChanged(acc, oldAcc)) {
accountsWithAddressChange.add(acc);
}
}
if(!accountsWithAddressChange.isEmpty()) {
AccountService.updateChildAddresses(accountsWithAddressChange);
}
}
}
// Trigger
trigger AccountTrigger on Account (before insert, before update, before delete,
after insert, after update, after delete, after undelete) {
new AccountTriggerHandler().run();
}
```
## Lightning Component Patterns
### Container Component Pattern
**Purpose**: Separate data management from presentation
**Use Case**: Complex Lightning components
**Implementation**:
```javascript
// Container Component
({
doInit : function(component, event, helper) {
helper.loadData(component);
},
handleSave : function(component, event, helper) {
helper.saveData(component, event.getParam('recordData'));
},
handleRefresh : function(component, event, helper) {
helper.loadData(component);
}
})
// Presentation Component
<aura:component>
<aura:attribute name="record" type="Object"/>
<aura:registerEvent name="save" type="c:SaveEvent"/>
<lightning:card title="{!v.record.Name}">
<lightning:input label="Name" value="{!v.record.Name}"/>
<lightning:button label="Save" onclick="{!c.handleSave}"/>
</lightning:card>
</aura:component>
```
### Publisher-Subscriber Pattern
**Purpose**: Loose coupling between components
**Use Case**: Component communication
**Implementation**:
```javascript
// Message Service
// messageService.js
const subscribers = new Map();
export const subscribe = (channel, callback) => {
if (!subscribers.has(channel)) {
subscribers.set(channel, []);
}
subscribers.get(channel).push(callback);
};
export const publish = (channel, message) => {
if (subscribers.has(channel)) {
subscribers.get(channel).forEach(callback => {
callback(message);
});
}
};
// Publisher Component
import { publish } from 'c/messageService';
export default class Publisher extends LightningElement {
handleClick() {
publish('accountSelected', {
accountId: this.accountId,
accountName: this.accountName
});
}
}
// Subscriber Component
import { subscribe } from 'c/messageService';
export default class Subscriber extends LightningElement {
connectedCallback() {
subscribe('accountSelected', (message) => {
this.selectedAccountId = message.accountId;
this.selectedAccountName = message.accountName;
});
}
}
```
## Best Practices
1. **Choose Appropriate Patterns**: Not every problem needs a pattern
2. **Keep It Simple**: Don't over-engineer solutions
3. **Document Pattern Usage**: Help future developers understand
4. **Consider Performance**: Patterns can add overhead
5. **Follow Salesforce Limits**: Respect governor limits
6. **Test Thoroughly**: Patterns can hide complexity
7. **Maintain Consistency**: Use patterns consistently across codebase
8. **Refactor When Needed**: Don't force patterns where they don't fit
9. **Team Agreement**: Ensure team understands patterns used
10. **Evolution**: Patterns should evolve with requirements