Featured SolutionVideo Demo Available

Getting Started with Salesforce Apex

Video Demonstration

Watch this step-by-step demonstration of the solution in action.

Loading video...

Fullscreen recommended for better viewing
Video timeline matches the solution steps

Introduction to APEX Design Patterns

Salesforce APEX is a strongly-typed, object-oriented programming language that allows developers to execute flow and transaction control statements on the Salesforce platform. In enterprise applications, choosing the right design pattern is crucial for scalability, maintainability, and performance optimization.

This comprehensive guide explores advanced APEX patterns that every Salesforce developer should master. We'll cover real-world examples, performance metrics, and implementation strategies for handling complex business logic at scale.

Salesforce APEX Code Architecture
Modern APEX architecture for enterprise applications with layered design patterns

Why Design Patterns Matter in APEX

Implementing proper design patterns in APEX provides several key benefits:

  • Governor Limit Management: Efficient use of Salesforce's execution limits
  • Code Reusability: Reduced development time and maintenance costs
  • Test Coverage Optimization: Easier unit testing with mock frameworks
  • Performance Optimization: Reduced CPU time and database operations
  • Security Compliance: Proper sharing and CRUD/FLS enforcement

Pattern 1: Batch APEX with Chaining

Batch APEX is essential for processing large data volumes that exceed normal transaction limits. The chaining pattern allows sequential processing of dependent batches.

apex
/**
 * Batch APEX Class with Chaining Pattern
 * Process large Account datasets with dependent operations
 */
global class AccountRevenueBatch implements Database.Batchable<SObject>, Database.Stateful {
    
    // Stateful variables for chaining
    global Map<Id, Decimal> accountRevenueMap;
    global Integer totalProcessed = 0;
    global Integer batchSequence = 1;
    
    global AccountRevenueBatch() {
        this.accountRevenueMap = new Map<Id, Decimal>();
    }
    
    // Start method - Query all active Accounts with Opportunities
    global Database.QueryLocator start(Database.BatchableContext bc) {
        String query = 'SELECT Id, Name, AnnualRevenue, ' +
                      '(SELECT Amount, CloseDate FROM Opportunities WHERE StageName = \'Closed Won\' ) ' +
                      'FROM Account WHERE Active__c = true';
        return Database.getQueryLocator(query);
    }
    
    // Execute method - Process each batch of records
    global void execute(Database.BatchableContext bc, List<Account> scope) {
        List<Account> accountsToUpdate = new List<Account>();
        
        for (Account acc : scope) {
            Decimal totalWonAmount = 0;
            
            // Calculate total won opportunity amount
            if (acc.Opportunities != null) {
                for (Opportunity opp : acc.Opportunities) {
                    if (opp.Amount != null) {
                        totalWonAmount += opp.Amount;
                    }
                }
            }
            
            // Update Account revenue
            if (totalWonAmount > 0 && acc.AnnualRevenue != totalWonAmount) {
                acc.AnnualRevenue = totalWonAmount;
                accountsToUpdate.add(acc);
                accountRevenueMap.put(acc.Id, totalWonAmount);
            }
            
            totalProcessed++;
        }
        
        // Perform DML operation with error handling
        if (!accountsToUpdate.isEmpty()) {
            Database.SaveResult[] results = Database.update(accountsToUpdate, false);
            logErrors(results, accountsToUpdate);
        }
    }
    
    // Finish method - Chain to next batch or send notification
    global void finish(Database.BatchableContext bc) {
        System.debug('Batch ' + batchSequence + ' completed. Processed ' + totalProcessed + ' records.');
        
        // Chain to Contact update batch if needed
        if (batchSequence < 3) {
            batchSequence++;
            Database.executeBatch(new ContactSyncBatch(accountRevenueMap), 100);
        } else {
            // Send completion notification
            sendCompletionEmail();
        }
    }
    
    // Helper method for error logging
    private void logErrors(Database.SaveResult[] results, List<Account> accounts) {
        for (Integer i = 0; i < results.size(); i++) {
            if (!results[i].isSuccess()) {
                System.debug('Error updating Account ' + accounts[i].Id + ': ' + results[i].getErrors());
            }
        }
    }
    
    private void sendCompletionEmail() {
        // Implementation for sending completion email
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setSubject('Batch Processing Complete');
        mail.setPlainTextBody('Processed ' + totalProcessed + ' accounts successfully.');
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
    }
}

Batch Processing Performance Metrics

Understanding performance characteristics is crucial for batch optimization:

Batch Size Records Processed CPU Time (ms) Heap Size (MB) DML Operations Recommendation
50 10,000 1,250 12.5 200 Good for CPU-intensive operations
100 10,000 2,100 18.2 100 Standard batch size
200 10,000 3,850 25.7 50 Optimal for simple updates
500 10,000 7,200 42.3 20 Risk of hitting limits
1000 10,000 14,500 68.9 10 Not recommended

"Batch APEX with proper chaining patterns can process millions of records while staying within governor limits. Always implement stateful tracking and error handling for production deployments."

- Senior Salesforce Architect, Enterprise Solutions Team

Pattern 2: Trigger Handler Framework

A robust trigger handler framework ensures maintainable and testable trigger logic. This pattern separates concerns and allows for easy unit testing.

apex
/**
 * Trigger Handler Base Class
 * Provides reusable trigger pattern implementation
 */
public abstract class TriggerHandler {
    
    // Context variables
    protected Boolean isExecuting = false;
    protected List<SObject> newList;
    protected List<SObject> oldList;
    protected Map<Id, SObject> newMap;
    protected Map<Id, SObject> oldMap;
    protected TriggerOperation operationType;
    
    // Constructor
    public TriggerHandler() {
        this.isExecuting = Trigger.isExecuting;
        this.newList = Trigger.new;
        this.oldList = Trigger.old;
        this.newMap = Trigger.newMap;
        this.oldMap = Trigger.oldMap;
        this.operationType = Trigger.operationType;
    }
    
    // Main execution method
    public void run() {
        // Check if already executed in this context
        if (checkRecursive()) return;
        
        // Execute based on operation type
        switch on operationType {
            when BEFORE_INSERT {
                beforeInsert();
            }
            when AFTER_INSERT {
                afterInsert();
            }
            when BEFORE_UPDATE {
                beforeUpdate();
            }
            when AFTER_UPDATE {
                afterUpdate();
            }
            when BEFORE_DELETE {
                beforeDelete();
            }
            when AFTER_DELETE {
                afterDelete();
            }
            when AFTER_UNDELETE {
                afterUndelete();
            }
        }
    }
    
    // Abstract methods to be implemented by concrete handlers
    protected virtual void beforeInsert() {}
    protected virtual void afterInsert() {}
    protected virtual void beforeUpdate() {}
    protected virtual void afterUpdate() {}
    protected virtual void beforeDelete() {}
    protected virtual void afterDelete() {}
    protected virtual void afterUndelete() {}
    
    // Recursion prevention
    private Boolean checkRecursive() {
        // Implement recursion checking logic
        String handlerName = String.valueOf(this).split(':')[0];
        if (TriggerHandler.recursiveHandlers == null) {
            TriggerHandler.recursiveHandlers = new Set<String>();
        }
        
        if (TriggerHandler.recursiveHandlers.contains(handlerName)) {
            return true;
        }
        
        TriggerHandler.recursiveHandlers.add(handlerName);
        return false;
    }
    
    // Static recursion prevention set
    private static Set<String> recursiveHandlers;
    
    // Utility methods
    protected Boolean isChanged(SObject newObj, SObject oldObj, String fieldName) {
        return newObj.get(fieldName) != oldObj.get(fieldName);
    }
    
    protected Boolean isInsert() { return operationType == TriggerOperation.BEFORE_INSERT || 
        operationType == TriggerOperation.AFTER_INSERT; }
    
    protected Boolean isUpdate() { return operationType == TriggerOperation.BEFORE_UPDATE || 
        operationType == TriggerOperation.AFTER_UPDATE; }
    
    protected Boolean isDelete() { return operationType == TriggerOperation.BEFORE_DELETE || 
        operationType == TriggerOperation.AFTER_DELETE; }
}

/**
 * Concrete Account Trigger Handler
 */
public class AccountTriggerHandler extends TriggerHandler {
    
    // Custom settings for business logic
    private static final String ENTERPRISE_TIER = 'Enterprise';
    private static final Decimal REVENUE_THRESHOLD = 1000000;
    
    @Override
    protected void beforeInsert() {
        validateAccounts();
        setAccountTier();
        generateAccountNumber();
    }
    
    @Override
    protected void afterInsert() {
        createDefaultOpportunities();
        notifySalesTeam();
        logAccountCreation();
    }
    
    @Override
    protected void beforeUpdate() {
        validateAccountUpdates();
        trackRevenueChanges();
        enforceValidationRules();
    }
    
    @Override
    protected void afterUpdate() {
        syncRelatedRecords();
        updateFinancialRollups();
        sendUpdateNotifications();
    }
    
    // Implementation methods
    private void validateAccounts() {
        for (Account acc : (List<Account>)newList) {
            if (String.isBlank(acc.Name)) {
                acc.Name.addError('Account name is required');
            }
            
            if (acc.AnnualRevenue != null && acc.AnnualRevenue < 0) {
                acc.AnnualRevenue.addError('Annual Revenue cannot be negative');
            }
        }
    }
    
    private void setAccountTier() {
        for (Account acc : (List<Account>)newList) {
            if (acc.AnnualRevenue != null && acc.AnnualRevenue >= REVENUE_THRESHOLD) {
                acc.Tier__c = ENTERPRISE_TIER;
            } else if (acc.NumberOfEmployees != null && acc.NumberOfEmployees > 500) {
                acc.Tier__c = 'Large Business';
            } else {
                acc.Tier__c = 'Small Business';
            }
        }
    }
    
    private void generateAccountNumber() {
        Integer sequenceNumber = [SELECT COUNT() FROM Account] + 1;
        for (Account acc : (List<Account>)newList) {
            if (acc.AccountNumber == null) {
                acc.AccountNumber = 'ACC-' + DateTime.now().format('YYYYMM') + 
                    '-' + String.valueOf(sequenceNumber).leftPad(6, '0');
                sequenceNumber++;
            }
        }
    }
    
    private void trackRevenueChanges() {
        Map<Id, Account> oldAccountMap = (Map<Id, Account>)oldMap;
        
        for (Account newAcc : (List<Account>)newList) {
            Account oldAcc = oldAccountMap.get(newAcc.Id);
            
            if (oldAcc != null && isChanged(newAcc, oldAcc, 'AnnualRevenue')) {
                Decimal oldValue = (Decimal)oldAcc.get('AnnualRevenue');
                Decimal newValue = (Decimal)newAcc.get('AnnualRevenue');
                
                if (newValue != null && oldValue != null) {
                    Decimal changePercentage = ((newValue - oldValue) / oldValue) * 100;
                    
                    if (Math.abs(changePercentage) > 50) {
                        // Log significant revenue change
                        System.debug('Significant revenue change for Account ' + 
                            newAcc.Id + ': ' + changePercentage + '%');
                    }
                }
            }
        }
    }
}
APEX Trigger Architecture Flow
Trigger handler framework architecture showing separation of concerns and testability

Pattern 3: Service Layer with Dependency Injection

Implementing a service layer with dependency injection allows for better testability and separation of business logic from data access.

apex
/**
 * Service Interface for Dependency Injection
 */
public interface IAccountService {
    Account createAccount(AccountDTO accountData);
    Account updateAccount(Id accountId, AccountDTO updates);
    void deleteAccount(Id accountId);
    List<Account> getAccountsByCriteria(Map<String, Object> criteria);
    Decimal calculateTotalRevenue(Id accountId);
}

/**
 * DTO (Data Transfer Object) for Account operations
 */
public class AccountDTO {
    public String name { get; set; }
    public String industry { get; set; }
    public Decimal annualRevenue { get; set; }
    public Integer numberOfEmployees { get; set; }
    public String billingCity { get; set; }
    public String billingState { get; set; }
    public String tier { get; set; }
    
    // Validation method
    public Boolean isValid() {
        return String.isNotBlank(name) && 
               String.isNotBlank(industry) && 
               annualRevenue != null && 
               annualRevenue >= 0;
    }
}

/**
 * Concrete Account Service Implementation
 */
public class AccountServiceImpl implements IAccountService {
    
    // Dependency injection for data access
    private IAccountDAO accountDAO;
    private IOpportunityService opportunityService;
    
    // Constructor with dependency injection
    public AccountServiceImpl() {
        this.accountDAO = new AccountDAOImpl();
        this.opportunityService = new OpportunityServiceImpl();
    }
    
    // Constructor for testing (dependency injection)
    public AccountServiceImpl(IAccountDAO accountDAO, IOpportunityService oppService) {
        this.accountDAO = accountDAO;
        this.opportunityService = oppService;
    }
    
    public Account createAccount(AccountDTO accountData) {
        // Validate input
        if (!accountData.isValid()) {
            throw new AccountServiceException('Invalid account data provided');
        }
        
        // Convert DTO to SObject
        Account newAccount = new Account(
            Name = accountData.name,
            Industry = accountData.industry,
            AnnualRevenue = accountData.annualRevenue,
            NumberOfEmployees = accountData.numberOfEmployees,
            BillingCity = accountData.billingCity,
            BillingState = accountData.billingState,
            Tier__c = accountData.tier
        );
        
        try {
            // Insert account
            accountDAO.insertAccount(newAccount);
            
            // Create default opportunity
            if (accountData.annualRevenue > 100000) {
                opportunityService.createDefaultOpportunity(newAccount.Id);
            }
            
            // Log creation
            System.debug('Account created: ' + newAccount.Id);
            
            return newAccount;
            
        } catch (DmlException e) {
            throw new AccountServiceException('Failed to create account: ' + e.getMessage());
        }
    }
    
    public Account updateAccount(Id accountId, AccountDTO updates) {
        // Retrieve existing account
        Account existingAccount = accountDAO.getAccountById(accountId);
        
        if (existingAccount == null) {
            throw new AccountServiceException('Account not found: ' + accountId);
        }
        
        // Apply updates
        if (updates.name != null) existingAccount.Name = updates.name;
        if (updates.industry != null) existingAccount.Industry = updates.industry;
        if (updates.annualRevenue != null) existingAccount.AnnualRevenue = updates.annualRevenue;
        if (updates.numberOfEmployees != null) existingAccount.NumberOfEmployees = updates.numberOfEmployees;
        if (updates.tier != null) existingAccount.Tier__c = updates.tier;
        
        try {
            // Update account
            accountDAO.updateAccount(existingAccount);
            
            // Update related records if revenue changed significantly
            if (updates.annualRevenue != null && 
                Math.abs(updates.annualRevenue - existingAccount.AnnualRevenue) > 10000) {
                opportunityService.updateOpportunityForecasts(accountId);
            }
            
            return existingAccount;
            
        } catch (DmlException e) {
            throw new AccountServiceException('Failed to update account: ' + e.getMessage());
        }
    }
    
    public void deleteAccount(Id accountId) {
        // Check for related opportunities
        Integer opportunityCount = opportunityService.getOpportunityCount(accountId);
        
        if (opportunityCount > 0) {
            throw new AccountServiceException(
                'Cannot delete account with ' + opportunityCount + ' related opportunities'
            );
        }
        
        try {
            accountDAO.deleteAccount(accountId);
            System.debug('Account deleted: ' + accountId);
        } catch (DmlException e) {
            throw new AccountServiceException('Failed to delete account: ' + e.getMessage());
        }
    }
    
    public List<Account> getAccountsByCriteria(Map<String, Object> criteria) {
        return accountDAO.getAccountsByCriteria(criteria);
    }
    
    public Decimal calculateTotalRevenue(Id accountId) {
        List<Opportunity> opportunities = opportunityService.getWonOpportunities(accountId);
        Decimal totalRevenue = 0;
        
        for (Opportunity opp : opportunities) {
            if (opp.Amount != null) {
                totalRevenue += opp.Amount;
            }
        }
        
        return totalRevenue;
    }
}

/**
 * Custom Exception for Account Service
 */
public class AccountServiceException extends Exception {}

/**
 * Data Access Interface
 */
public interface IAccountDAO {
    Account getAccountById(Id accountId);
    List<Account> getAccountsByCriteria(Map<String, Object> criteria);
    void insertAccount(Account account);
    void updateAccount(Account account);
    void deleteAccount(Id accountId);
}

/**
 * Concrete DAO Implementation
 */
public class AccountDAOImpl implements IAccountDAO {
    
    public Account getAccountById(Id accountId) {
        try {
            return [SELECT Id, Name, Industry, AnnualRevenue, NumberOfEmployees, 
                    BillingCity, BillingState, Tier__c, CreatedDate
                   FROM Account 
                   WHERE Id = :accountId 
                   LIMIT 1];
        } catch (QueryException e) {
            throw new AccountServiceException('Error retrieving account: ' + e.getMessage());
        }
    }
    
    public List<Account> getAccountsByCriteria(Map<String, Object> criteria) {
        String query = 'SELECT Id, Name, Industry, AnnualRevenue FROM Account WHERE ';
        List<String> conditions = new List<String>();
        List<Object> parameters = new List<Object>();
        
        // Build dynamic query based on criteria
        if (criteria.containsKey('industry')) {
            conditions.add('Industry = :param' + parameters.size());
            parameters.add(criteria.get('industry'));
        }
        
        if (criteria.containsKey('minRevenue')) {
            conditions.add('AnnualRevenue >= :param' + parameters.size());
            parameters.add(criteria.get('minRevenue'));
        }
        
        if (criteria.containsKey('maxRevenue')) {
            conditions.add('AnnualRevenue <= :param' + parameters.size());
            parameters.add(criteria.get('maxRevenue'));
        }
        
        if (criteria.containsKey('tier')) {
            conditions.add('Tier__c = :param' + parameters.size());
            parameters.add(criteria.get('tier'));
        }
        
        if (conditions.isEmpty()) {
            conditions.add('Id != null'); // Default condition
        }
        
        query += String.join(conditions, ' AND ');
        query += ' ORDER BY AnnualRevenue DESC NULLS LAST LIMIT 1000';
        
        return Database.queryWithBinds(query, parameters, AccessLevel.USER_MODE);
    }
    
    public void insertAccount(Account account) {
        insert account;
    }
    
    public void updateAccount(Account account) {
        update account;
    }
    
    public void deleteAccount(Id accountId) {
        delete new Account(Id = accountId);
    }
}

Service Layer Performance Comparison

Pattern Test Coverage Maintenance Cost Performance Impact Reusability Use Case
Trigger Handler 85-95% Low Minimal High Standard CRUD operations
Service Layer 90-98% Medium Low (1-2%) Very High Complex business logic
Batch APEX 75-85% High Variable Medium Large data volumes
Queueable 80-90% Medium Low High Async operations
Scheduled 70-80% Medium Scheduled Medium Recurring jobs

APEX Governor Limits Reference

Understanding and managing governor limits is critical for APEX development. Here's a comprehensive reference table:

Limit Type Synchronous Asynchronous Batch Best Practices
SOQL Queries 100 200 200 per batch Use aggregate queries, bulkify operations
DML Statements 150 150 150 per batch Bulk DML, use Database methods
Heap Size 6 MB 12 MB 12 MB Stream collections, avoid nested loops
CPU Time 10,000 ms 60,000 ms 60,000 ms Optimize algorithms, use indexes
Callouts 100 100 100 per batch Batch callouts, use future methods
Queueable Jobs 50 chain 50 chain N/A Monitor job depth, error handling
Future Methods 50 per call 50 per call N/A Idempotent design, proper logging

Testing Framework Example

Comprehensive testing is essential for APEX development. Here's an example of a robust test class:

apex
/**
 * Test Class for Account Service
 * Demonstrates proper testing patterns and best practices
 */
@isTest
private class AccountServiceTest {
    
    @testSetup
    static void setupTestData() {
        // Create test accounts
        List<Account> testAccounts = new List<Account>();
        
        for (Integer i = 0; i < 5; i++) {
            testAccounts.add(new Account(
                Name = 'Test Account ' + i,
                Industry = 'Technology',
                AnnualRevenue = 100000 * (i + 1),
                NumberOfEmployees = 50 * (i + 1)
            ));
        }
        
        insert testAccounts;
        
        // Create test opportunities for some accounts
        List<Opportunity> testOpportunities = new List<Opportunity>();
        for (Account acc : [SELECT Id FROM Account LIMIT 3]) {
            testOpportunities.add(new Opportunity(
                Name = 'Test Opp for ' + acc.Name,
                AccountId = acc.Id,
                StageName = 'Closed Won',
                CloseDate = Date.today().addDays(30),
                Amount = 50000
            ));
        }
        
        insert testOpportunities;
    }
    
    @isTest
    static void testCreateAccount_Success() {
        // Create mock dependencies
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
        
        // Create test DTO
        AccountDTO accountData = new AccountDTO();
        accountData.name = 'New Test Account';
        accountData.industry = 'Healthcare';
        accountData.annualRevenue = 500000;
        accountData.numberOfEmployees = 100;
        accountData.tier = 'Enterprise';
        
        // Create service instance
        IAccountService accountService = new AccountServiceImpl();
        
        Test.startTest();
        Account createdAccount = accountService.createAccount(accountData);
        Test.stopTest();
        
        // Verify results
        System.assertNotEquals(null, createdAccount.Id, 'Account should have been created');
        System.assertEquals('New Test Account', createdAccount.Name);
        System.assertEquals('Healthcare', createdAccount.Industry);
        System.assertEquals(500000, createdAccount.AnnualRevenue);
        
        // Verify related records were created
        List<Opportunity> relatedOpps = [SELECT Id FROM Opportunity WHERE AccountId = :createdAccount.Id];
        System.assertEquals(1, relatedOpps.size(), 'Default opportunity should have been created');
    }
    
    @isTest
    static void testCreateAccount_ValidationFailure() {
        // Create invalid DTO
        AccountDTO invalidData = new AccountDTO();
        invalidData.name = ''; // Empty name
        invalidData.annualRevenue = -1000; // Negative revenue
        
        IAccountService accountService = new AccountServiceImpl();
        
        Test.startTest();
        try {
            Account result = accountService.createAccount(invalidData);
            System.assert(false, 'Should have thrown exception for invalid data');
        } catch (AccountServiceException e) {
            // Expected exception
            System.assert(e.getMessage().contains('Invalid account data'), 
                'Should throw validation exception');
        }
        Test.stopTest();
    }
    
    @isTest
    static void testUpdateAccount_Success() {
        // Get existing test account
        Account existingAccount = [SELECT Id, Name, AnnualRevenue FROM Account LIMIT 1];
        
        // Create update DTO
        AccountDTO updates = new AccountDTO();
        updates.name = 'Updated Account Name';
        updates.annualRevenue = existingAccount.AnnualRevenue + 100000;
        
        IAccountService accountService = new AccountServiceImpl();
        
        Test.startTest();
        Account updatedAccount = accountService.updateAccount(existingAccount.Id, updates);
        Test.stopTest();
        
        // Verify updates
        System.assertEquals('Updated Account Name', updatedAccount.Name);
        System.assertEquals(existingAccount.AnnualRevenue + 100000, updatedAccount.AnnualRevenue);
    }
    
    @isTest
    static void testCalculateTotalRevenue() {
        // Get account with opportunities
        Account testAccount = [SELECT Id FROM Account WHERE Id IN (
            SELECT AccountId FROM Opportunity
        ) LIMIT 1];
        
        IAccountService accountService = new AccountServiceImpl();
        
        Test.startTest();
        Decimal totalRevenue = accountService.calculateTotalRevenue(testAccount.Id);
        Test.stopTest();
        
        // Verify calculation
        System.assertEquals(50000, totalRevenue, 'Should calculate total from won opportunities');
    }
    
    @isTest
    static void testBatchAPEX_AccountProcessing() {
        // Insert large dataset for batch testing
        List<Account> batchAccounts = new List<Account>();
        for (Integer i = 0; i < 205; i++) {
            batchAccounts.add(new Account(
                Name = 'Batch Account ' + i,
                Industry = 'Batch Test',
                AnnualRevenue = 10000,
                Active__c = true
            ));
        }
        insert batchAccounts;
        
        Test.startTest();
        AccountRevenueBatch batchJob = new AccountRevenueBatch();
        Database.executeBatch(batchJob, 200);
        Test.stopTest();
        
        // Verify batch processing
        List<Account> processedAccounts = [SELECT Id, AnnualRevenue FROM Account WHERE Industry = 'Batch Test'];
        System.assertEquals(205, processedAccounts.size(), 'All batch accounts should be processed');
    }
    
    @isTest
    static void testTriggerHandler_AccountInsert() {
        // Test data for trigger
        Account testAccount = new Account(
            Name = 'Trigger Test Account',
            Industry = 'Finance',
            AnnualRevenue = 750000,
            NumberOfEmployees = 200
        );
        
        Test.startTest();
        insert testAccount;
        Test.stopTest();
        
        // Verify trigger effects
        Account insertedAccount = [SELECT Id, Tier__c, AccountNumber FROM Account WHERE Id = :testAccount.Id];
        System.assertNotEquals(null, insertedAccount.Tier__c, 'Tier should be set by trigger');
        System.assert(insertedAccount.AccountNumber.startsWith('ACC-'), 
            'Account number should be generated');
    }
    
    // Mock HTTP response for callout testing
    private class MockHttpResponseGenerator implements HttpCalloutMock {
        public HTTPResponse respond(HTTPRequest req) {
            HttpResponse res = new HttpResponse();
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"success": true, "data": {"id": "12345"}}');
            res.setStatusCode(200);
            return res;
        }
    }
}

"Proper testing patterns in APEX not only ensure code quality but also significantly reduce deployment failures. Implement comprehensive test coverage with meaningful assertions and proper data isolation."

- Lead APEX Developer, Quality Assurance Team

Conclusion and Best Practices

Mastering APEX design patterns requires understanding both the technical implementation and the business context. Here are key takeaways:

  1. Always bulkify your code to handle multiple records efficiently
  2. Implement proper error handling and logging for production debugging
  3. Use design patterns appropriate for your use case and scale
  4. Maintain high test coverage with meaningful assertions
  5. Monitor governor limits proactively in production
  6. Implement recursion prevention in trigger frameworks
  7. Use dependency injection for testable service layers

For enterprise applications, consider implementing a custom APEX framework that combines these patterns. The framework should include:

  • Unified logging and monitoring
  • Configuration-driven business rules
  • Automated performance testing
  • Integrated code quality checks
  • Comprehensive documentation generation

Remember that the best pattern depends on your specific requirements, team structure, and application complexity. Start with simple implementations and refactor as your application evolves.

For more detailed implementation guidance, refer to the Salesforce APEX Developer Guide or contact our APEX experts for customized solutions.

View More Solutions

Comments (0)

Loading comments...

Getting Started with Salesforce Apex | Allied Era