UNPKG

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
# 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