Speaking twice at Dreamforce · Sept 15-17 →

Guide

Salesforce implementation. Build around the real work.

Implementations rarely fail on the platform. They fail because the org got built around a demo instead of the work your team actually does.

Get the written assessment

Certified Partner since 2010 · MVP Hall of Fame · 200+ agents in production · UAE and US desks

What actually goes wrong

A Salesforce implementation rarely fails on the platform. It fails because someone configured the demo instead of the desk. The process the org was supposed to hold never got written down, so every release turns into an argument about what the fields mean and who owns the record.

That is the expensive part, and it is settled in the first two weeks. Name who does the work today, which object it should live on, and what has to stay outside Salesforce. Then configure, and reach for Apex only when configuration cannot carry the rule. The sections below follow that order: planning, development standards, security, testing, and deployment. When we take this work, the first week is spent in your org reading what already exists — not in a workshop.

Pre-Implementation Planning

Requirements Gathering and Analysis

Before anyone writes a line of code, get the current process out of people's heads and onto paper:

### Business Process Mapping
- Document current state workflows
- Identify pain points and inefficiencies
- Define future state processes
- Map processes to Salesforce capabilities

Technical Architecture Planning

# Architecture Decision Record Template
Decision: Choice of Salesforce Edition
Status: Approved
Context: 
  - 500 users expected
  - Complex approval workflows needed
  - API integration requirements
  - Advanced analytics required
Decision: Enterprise Edition with additional licenses
Rationale: Provides necessary API calls and workflow limits
Consequences: Higher licensing cost but meets all requirements

Data Model Design Principles

1. Object Relationship Design
- Use master-detail relationships for tight coupling
- Implement lookup relationships for loose coupling
- Consider junction objects for many-to-many relationships

  1. Field Naming Conventions
   Standard Format: [Object]_[Field_Purpose]_[Type]
   Examples:
   - Account_Annual_Revenue_Currency__c
   - Contact_Preferred_Channel_Picklist__c
   - Opportunity_Close_Probability_Percent__c
   

3. Record Type Strategy
- Define record types based on business processes
- Limit to 200 record types per object
- Use page layouts effectively

Development Best Practices

Apex Development Standards

Code Organization and Structure

/**
 * @description OpportunityTriggerHandler manages all trigger operations for Opportunity
 * @author Your Team
 * @date 2024-01-15
 */
public with sharing class OpportunityTriggerHandler extends TriggerHandler {
    
    // Constructor
    public OpportunityTriggerHandler() {
        super();
    }
    
    // Override methods
    public override void beforeInsert() {
        OpportunityValidator.validateNewOpportunities(Trigger.new);
        OpportunityEnricher.setDefaultValues(Trigger.new);
    }
    
    public override void afterUpdate() {
        OpportunityNotifier.notifyOwnerChanges(
            Trigger.new, 
            (Map<Id, Opportunity>) Trigger.oldMap
        );
    }
}

Bulkification Patterns

Always write bulkified code to handle multiple records:

// BAD: Not bulkified
public static void updateAccountRating(List<Account> accounts) {
    for (Account acc : accounts) {
        List<Opportunity> opps = [
            SELECT Id, Amount 
            FROM Opportunity 
            WHERE AccountId = :acc.Id
        ];
        // Process opportunities
    }
}

// GOOD: Bulkified
public static void updateAccountRating(List<Account> accounts) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : accounts) {
        accountIds.add(acc.Id);
    }
    
    Map<Id, List<Opportunity>> oppsByAccountId = new Map<Id, List<Opportunity>>();
    for (Opportunity opp : [
        SELECT Id, Amount, AccountId 
        FROM Opportunity 
        WHERE AccountId IN :accountIds
    ]) {
        if (!oppsByAccountId.containsKey(opp.AccountId)) {
            oppsByAccountId.put(opp.AccountId, new List<Opportunity>());
        }
        oppsByAccountId.get(opp.AccountId).add(opp);
    }
    
    // Process accounts with their opportunities
    for (Account acc : accounts) {
        List<Opportunity> accountOpps = oppsByAccountId.get(acc.Id);
        if (accountOpps != null) {
            // Process opportunities
        }
    }
}

Lightning Web Components Best Practices

// accountManager.js
import { LightningElement, wire, track } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

    @track accounts;
    @track error;
    wiredAccountsResult;
    
    @wire(getAccounts)
    wiredAccounts(result) {
        this.wiredAccountsResult = result;
        if (result.data) {
            this.accounts = result.data;
            this.error = undefined;
        } else if (result.error) {
            this.error = result.error;
            this.accounts = undefined;
            this.showErrorToast();
        }
    }
    
    handleRefresh() {
        return refreshApex(this.wiredAccountsResult);
    }
    
    showErrorToast() {
        const evt = new ShowToastEvent({
            title: 'Error loading accounts',
            message: this.error.body.message,
            variant: 'error',
        });
        this.dispatchEvent(evt);
    }
}

Security and Governance

Security Model Implementation

Sharing Rules Architecture

/**
 * Implement row-level security using Apex managed sharing
 */
public class OpportunitySharingManager {
    
    public static void shareWithTeamMembers(List<Opportunity> opportunities) {
        List<OpportunityShare> sharesToInsert = new List<OpportunityShare>();
        
        // Get team members for each opportunity
        Map<Id, Set<Id>> teamMembersByOppId = getTeamMembers(opportunities);
        
        for (Opportunity opp : opportunities) {
            Set<Id> teamMembers = teamMembersByOppId.get(opp.Id);
            if (teamMembers != null) {
                for (Id userId : teamMembers) {
                    OpportunityShare oppShare = new OpportunityShare();
                    oppShare.OpportunityId = opp.Id;
                    oppShare.UserOrGroupId = userId;
                    oppShare.OpportunityAccessLevel = 'Edit';
                    oppShare.RowCause = Schema.OpportunityShare.RowCause.Manual;
                    sharesToInsert.add(oppShare);
                }
            }
        }
        
        if (!sharesToInsert.isEmpty()) {
            Database.SaveResult[] results = Database.insert(sharesToInsert, false);
            handleShareResults(results);
        }
    }
}

Field-Level Security Best Practices

1. Principle of Least Privilege
- Grant minimum necessary permissions
- Use permission sets for granular control
- Regular security audits

  1. CRUD/FLS Enforcement
   // Enforce CRUD and FLS in Apex
   public with sharing class AccountService {
       public static List<Account> getAccessibleAccounts() {
           // Check object accessibility
           if (!Schema.sObjectType.Account.isAccessible()) {
               throw new AuraHandledException('Insufficient privileges');
           }
           
           // Check field accessibility
           Set<String> fieldsToCheck = new Set<String>{
               'Name', 'Industry', 'AnnualRevenue'
           };
           
           Map<String, Schema.SObjectField> fieldMap = 
               Schema.SObjectType.Account.fields.getMap();
           
           for (String fieldName : fieldsToCheck) {
               if (!fieldMap.get(fieldName).getDescribe().isAccessible()) {
                   throw new AuraHandledException(
                       'No access to field: ' + fieldName
                   );
               }
           }
           
           return [SELECT Id, Name, Industry, AnnualRevenue FROM Account];
       }
   }
   

Testing Strategies

What to test, and what a passing suite does not prove

Unit Testing Best Practices

@isTest
private class OpportunityTriggerHandlerTest {
    
    @testSetup
    static void setup() {
        // Create test data factory
        List<Account> testAccounts = TestDataFactory.createAccounts(200);
        insert testAccounts;
        
        List<Opportunity> testOpps = TestDataFactory.createOpportunities(
            testAccounts, 
            5  // 5 opportunities per account
        );
        insert testOpps;
    }
    
    @isTest
    static void testBulkInsert() {
        // Given: 1000 new opportunities
        List<Opportunity> newOpps = TestDataFactory.createOpportunities(
            [SELECT Id FROM Account], 
            5
        );
        
        // When: Inserting opportunities in bulk
        Test.startTest();
        insert newOpps;
        Test.stopTest();
        
        // Then: Verify trigger logic executed correctly
        List<Opportunity> insertedOpps = [
            SELECT Id, StageName, Probability 
            FROM Opportunity 
            WHERE Id IN :newOpps
        ];
        
        System.assertEquals(1000, insertedOpps.size());
        for (Opportunity opp : insertedOpps) {
            System.assertEquals(
                OpportunityHelper.getProbabilityByStage(opp.StageName), 
                opp.Probability,
                'Probability should match stage'
            );
        }
    }
    
    @isTest
    static void testGovernorLimits() {
        // Test with maximum DML rows
        List<Opportunity> largeDataSet = new List<Opportunity>();
        for (Integer i = 0; i < 10000; i++) {
            largeDataSet.add(TestDataFactory.createOpportunity());
        }
        
        Test.startTest();
        // This should handle governor limits gracefully
        OpportunityBatchProcessor batch = new OpportunityBatchProcessor(largeDataSet);
        Database.executeBatch(batch, 200);
        Test.stopTest();
        
        // Verify batch processed successfully
        System.assertEquals(10000, [SELECT COUNT() FROM Opportunity]);
    }
}

Integration Testing Patterns

@isTest
private class ExternalSystemIntegrationTest {
    
    @isTest
    static void testSuccessfulCallout() {
        // Set mock callout class
        Test.setMock(HttpCalloutMock.class, new ExternalSystemSuccessMock());
        
        Account testAccount = TestDataFactory.createAccount();
        insert testAccount;
        
        Test.startTest();
        ExternalSystemService.syncAccount(testAccount.Id);
        Test.stopTest();
        
        // Verify integration log created
        Integration_Log__c log = [
            SELECT Status__c, Response_Code__c 
            FROM Integration_Log__c 
            WHERE Account__c = :testAccount.Id
        ];
        
        System.assertEquals('Success', log.Status__c);
        System.assertEquals(200, log.Response_Code__c);
    }
    
    private class ExternalSystemSuccessMock implements HttpCalloutMock {
        public HTTPResponse respond(HTTPRequest req) {
            HttpResponse res = new HttpResponse();
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"status":"success","id":"EXT-12345"}');
            res.setStatusCode(200);
            return res;
        }
    }
}

Deployment and Release Management

CI/CD Pipeline Configuration

Salesforce DX Project Structure

project-root/
├── .forceignore
├── .gitignore
├── sfdx-project.json
├── config/
│   └── project-scratch-def.json
├── force-app/
│   └── main/
│       └── default/
│           ├── classes/
│           ├── triggers/
│           ├── lwc/
│           ├── aura/
│           └── objects/
├── scripts/
│   ├── apex/
│   └── soql/
└── tests/
    └── apex/

GitHub Actions Deployment Pipeline

name: Salesforce CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Salesforce CLI
        run: |
          npm install sfdx-cli@latest --global
          
      - name: Authenticate to Org
        run: |
          echo ${{ secrets.SFDX_AUTH_URL }} > auth.url
          sfdx auth:sfdxurl:store -f auth.url -a targetOrg
          
      - name: Run Apex Tests
        run: |
          sfdx force:apex:test:run             --targetusername targetOrg             --codecoverage             --resultformat json             --wait 20 > test-results.json
            
      - name: Check Code Coverage
        run: |
          coverage=$(cat test-results.json | jq .result.summary.orgWideCoverage)
          if (( $(echo "$coverage < 75" | bc -l) )); then
            echo "Code coverage is below 75%"
            exit 1
          fi
          
      - name: Deploy to Org
        if: github.ref == 'refs/heads/main'
        run: |
          sfdx force:source:deploy             --targetusername targetOrg             --sourcepath force-app

Deployment Checklist

1. Pre-Deployment Validation
- Run all unit tests in sandbox
- Validate code coverage > 75%
- Check for deployment errors
- Review debug logs for exceptions

  1. Deployment Steps
   # Validate deployment without committing
   sfdx force:source:deploy -c -p force-app -u production
   
   # Deploy with specific tests
   sfdx force:source:deploy -p force-app -u production      -l RunSpecifiedTests -r Test1,Test2,Test3
   
   # Quick deploy after validation
   sfdx force:source:deploy -q <validationId> -u production
   

The brief

Send the messy version.

The honest no: if the work is a software factory with a named delivery date, we are the wrong partner.