10 min read

Salesforce Integration Patterns: Architecture Guide for Technical Leaders

Master Salesforce integration patterns with detailed architectural guidance, code examples, and best practices for enterprise system integration.

Understanding Salesforce Integration Architecture

Salesforce integration is critical for creating a unified view of your business data and processes. This guide explores proven integration patterns, architectural considerations, and implementation strategies for connecting Salesforce with your enterprise systems. Modern enterprises typically integrate Salesforce with 5-10 external systems, making robust integration architecture essential for success. Whether you're synchronizing data with ERP systems, connecting marketing automation platforms, or building custom APIs, choosing the right integration pattern is crucial.

Core Integration Patterns

## Request-Reply Pattern The Request-Reply pattern is ideal for real-time, synchronous integrations where immediate response is required. ### Implementation Example: REST API Integration ```apex public class ExternalSystemService { @future(callout=true) public static void syncAccountToERP(String accountId) { Account acc = [ SELECT Id, Name, BillingAddress, Industry, AnnualRevenue FROM Account WHERE Id = :accountId ]; // Prepare request HttpRequest req = new HttpRequest(); req.setEndpoint('callout:ERP_System/api/customers'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setHeader('Authorization', 'Bearer {!$Credential.ERP_System.token}'); // Build request body Map<String, Object> requestBody = new Map<String, Object>{ 'externalId' => acc.Id, 'companyName' => acc.Name, 'industry' => acc.Industry, 'annualRevenue' => acc.AnnualRevenue, 'address' => new Map<String, Object>{ 'street' => acc.BillingStreet, 'city' => acc.BillingCity, 'state' => acc.BillingState, 'postalCode' => acc.BillingPostalCode, 'country' => acc.BillingCountry } }; req.setBody(JSON.serialize(requestBody)); req.setTimeout(120000); // 2 minute timeout try { Http http = new Http(); HttpResponse res = http.send(req); if (res.getStatusCode() == 200 || res.getStatusCode() == 201) { // Parse response Map<String, Object> responseBody = (Map<String, Object>) JSON.deserializeUntyped(res.getBody()); // Update Account with ERP ID acc.ERP_Customer_ID__c = (String) responseBody.get('customerId'); acc.ERP_Sync_Status__c = 'Synced'; acc.ERP_Last_Sync__c = DateTime.now(); update acc; // Log success createIntegrationLog(acc.Id, 'Success', res.getBody()); } else { // Handle error createIntegrationLog(acc.Id, 'Error', res.getBody()); throw new IntegrationException( 'ERP sync failed: ' + res.getStatus() ); } } catch (Exception e) { createIntegrationLog(acc.Id, 'Exception', e.getMessage()); throw e; } } } ``` ## Fire-and-Forget Pattern Use this pattern for asynchronous operations where immediate response isn't required. ### Platform Events Implementation ```apex // Define Platform Event // API Name: Account_Change_Event__e // Fields: Account_Id__c, Change_Type__c, Changed_Fields__c // Publisher Class public class AccountEventPublisher { public static void publishAccountChanges( List<Account> newAccounts, Map<Id, Account> oldAccountsMap ) { List<Account_Change_Event__e> events = new List<Account_Change_Event__e>(); for (Account acc : newAccounts) { Account oldAcc = oldAccountsMap?.get(acc.Id); // Detect changes Set<String> changedFields = new Set<String>(); if (oldAcc != null) { if (acc.Name != oldAcc.Name) changedFields.add('Name'); if (acc.Industry != oldAcc.Industry) changedFields.add('Industry'); if (acc.AnnualRevenue != oldAcc.AnnualRevenue) changedFields.add('AnnualRevenue'); } if (!changedFields.isEmpty() || oldAcc == null) { Account_Change_Event__e event = new Account_Change_Event__e( Account_Id__c = acc.Id, Change_Type__c = oldAcc == null ? 'INSERT' : 'UPDATE', Changed_Fields__c = String.join(new List<String>(changedFields), ',') ); events.add(event); } } if (!events.isEmpty()) { List<Database.SaveResult> results = EventBus.publish(events); // Check publishing results for (Database.SaveResult sr : results) { if (!sr.isSuccess()) { // Log publishing errors System.debug('Event publishing failed: ' + sr.getErrors()[0].getMessage()); } } } } } // Subscriber (External System) // Subscribe to: /event/Account_Change_Event__e // Example Node.js subscriber using jsforce ```javascript const jsforce = require('jsforce'); const conn = new jsforce.Connection({ loginUrl: 'https://login.salesforce.com', version: '57.0' }); conn.login(username, password, (err, userInfo) => { if (err) return console.error(err); // Subscribe to platform event const subscription = conn.streaming.topic("/event/Account_Change_Event__e") .subscribe((message) => { console.log('Received account change:', message); // Process the change processAccountChange({ accountId: message.payload.Account_Id__c, changeType: message.payload.Change_Type__c, changedFields: message.payload.Changed_Fields__c }); }); subscription.on('error', (error) => { console.error('Subscription error:', error); }); }); ```

Batch Data Synchronization

## Batch Integration Patterns For large-scale data synchronization, batch processing provides optimal performance and resource utilization. ### Bulk API 2.0 Implementation ```apex // Batch Apex for Data Export public class AccountDataExportBatch implements Database.Batchable<sObject>, Database.Stateful, Database.AllowsCallouts { private String query; private String targetSystem; private List<String> errors = new List<String>(); public AccountDataExportBatch(String query, String targetSystem) { this.query = query; this.targetSystem = targetSystem; } public Database.QueryLocator start(Database.BatchableContext bc) { return Database.getQueryLocator(query); } public void execute(Database.BatchableContext bc, List<Account> scope) { // Prepare bulk data List<Map<String, Object>> bulkData = new List<Map<String, Object>>(); for (Account acc : scope) { Map<String, Object> record = new Map<String, Object>{ 'salesforce_id' => acc.Id, 'name' => acc.Name, 'industry' => acc.Industry, 'annual_revenue' => acc.AnnualRevenue, 'number_of_employees' => acc.NumberOfEmployees, 'website' => acc.Website, 'phone' => acc.Phone, 'billing_address' => new Map<String, Object>{ 'street' => acc.BillingStreet, 'city' => acc.BillingCity, 'state' => acc.BillingState, 'postal_code' => acc.BillingPostalCode, 'country' => acc.BillingCountry }, 'last_modified_date' => acc.LastModifiedDate }; bulkData.add(record); } // Send to external system try { HttpRequest req = new HttpRequest(); req.setEndpoint('callout:' + targetSystem + '/api/bulk/accounts'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(JSON.serialize(bulkData)); req.setTimeout(120000); Http http = new Http(); HttpResponse res = http.send(req); if (res.getStatusCode() != 200) { errors.add('Batch failed: ' + res.getBody()); } } catch (Exception e) { errors.add('Exception in batch: ' + e.getMessage()); } } public void finish(Database.BatchableContext bc) { // Send summary email Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setToAddresses(new String[] { UserInfo.getUserEmail() }); mail.setSubject('Account Data Export Completed'); String body = 'Export to ' + targetSystem + ' completed.\n\n'; if (!errors.isEmpty()) { body += 'Errors encountered:\n' + String.join(errors, '\n'); } else { body += 'All batches processed successfully.'; } mail.setPlainTextBody(body); Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); } } // Schedule the batch job AccountDataExportBatch batch = new AccountDataExportBatch( 'SELECT Id, Name, Industry, AnnualRevenue, NumberOfEmployees, ' + 'Website, Phone, BillingStreet, BillingCity, BillingState, ' + 'BillingPostalCode, BillingCountry, LastModifiedDate ' + 'FROM Account WHERE LastModifiedDate >= LAST_N_DAYS:1', 'ERP_System' ); Database.executeBatch(batch, 200); ``` ### Change Data Capture (CDC) Pattern ```apex // Enable CDC for Account object in Setup // Subscribe to change events // CDC Event Handler public class AccountCDCHandler { public static void handleChanges(List<AccountChangeEvent> changes) { List<Integration_Queue__c> queueItems = new List<Integration_Queue__c>(); for (AccountChangeEvent event : changes) { // Get change header EventBus.ChangeEventHeader header = event.ChangeEventHeader; // Process based on change type String changeType = header.changeType; List<String> recordIds = header.recordIds; for (String recordId : recordIds) { Integration_Queue__c queueItem = new Integration_Queue__c( Record_Id__c = recordId, Object_Type__c = 'Account', Operation__c = changeType, Status__c = 'Pending', Changed_Fields__c = String.join(header.changedFields, ','), Retry_Count__c = 0 ); queueItems.add(queueItem); } } if (!queueItems.isEmpty()) { insert queueItems; // Trigger queue processor System.enqueueJob(new IntegrationQueueProcessor(queueItems[0].Id)); } } } ```

API Design and Governance

## RESTful API Design ### Custom REST API Implementation ```apex @RestResource(urlMapping='/api/v1/accounts/*') global with sharing class AccountRestService { @HttpGet global static AccountWrapper getAccount() { RestRequest req = RestContext.request; String accountId = req.requestURI.substringAfterLast('/'); try { Account acc = [ SELECT Id, Name, Industry, AnnualRevenue, Website, (SELECT Id, Name, Email, Title FROM Contacts LIMIT 5) FROM Account WHERE Id = :accountId ]; return new AccountWrapper(acc); } catch (Exception e) { RestResponse res = RestContext.response; res.statusCode = 404; res.responseBody = Blob.valueOf(JSON.serialize( new ErrorResponse('Account not found', e.getMessage()) )); return null; } } @HttpPost global static ResponseWrapper createAccount(AccountWrapper accountData) { try { // Validate input ValidationResult validation = validateAccountData(accountData); if (!validation.isValid) { RestResponse res = RestContext.response; res.statusCode = 400; return new ResponseWrapper(false, validation.errors); } // Create account Account acc = new Account( Name = accountData.name, Industry = accountData.industry, AnnualRevenue = accountData.annualRevenue, Website = accountData.website ); insert acc; // Create contacts if provided if (accountData.contacts != null && !accountData.contacts.isEmpty()) { List<Contact> contacts = new List<Contact>(); for (ContactWrapper cw : accountData.contacts) { contacts.add(new Contact( AccountId = acc.Id, FirstName = cw.firstName, LastName = cw.lastName, Email = cw.email, Title = cw.title )); } insert contacts; } RestResponse res = RestContext.response; res.statusCode = 201; res.addHeader('Location', '/api/v1/accounts/' + acc.Id); return new ResponseWrapper(true, 'Account created successfully', acc.Id); } catch (Exception e) { RestResponse res = RestContext.response; res.statusCode = 500; return new ResponseWrapper(false, e.getMessage()); } } @HttpPatch global static ResponseWrapper updateAccount(Map<String, Object> fields) { RestRequest req = RestContext.request; String accountId = req.requestURI.substringAfterLast('/'); try { Account acc = [SELECT Id FROM Account WHERE Id = :accountId]; // Dynamic field update for (String fieldName : fields.keySet()) { // Validate field exists and is updateable Schema.SObjectField field = Schema.Account.sObjectType .getDescribe().fields.getMap().get(fieldName); if (field != null && field.getDescribe().isUpdateable()) { acc.put(fieldName, fields.get(fieldName)); } else { throw new ApiException('Invalid or non-updateable field: ' + fieldName); } } update acc; return new ResponseWrapper(true, 'Account updated successfully'); } catch (Exception e) { RestResponse res = RestContext.response; res.statusCode = 400; return new ResponseWrapper(false, e.getMessage()); } } // Wrapper classes global class AccountWrapper { public String id; public String name; public String industry; public Decimal annualRevenue; public String website; public List<ContactWrapper> contacts; public AccountWrapper(Account acc) { this.id = acc.Id; this.name = acc.Name; this.industry = acc.Industry; this.annualRevenue = acc.AnnualRevenue; this.website = acc.Website; this.contacts = new List<ContactWrapper>(); for (Contact c : acc.Contacts) { this.contacts.add(new ContactWrapper(c)); } } } global class ContactWrapper { public String id; public String firstName; public String lastName; public String email; public String title; public ContactWrapper(Contact c) { this.id = c.Id; this.firstName = c.FirstName; this.lastName = c.LastName; this.email = c.Email; this.title = c.Title; } } } ``` ### API Versioning Strategy ```apex // Version 2 of the API with backward compatibility @RestResource(urlMapping='/api/v2/accounts/*') global with sharing class AccountRestServiceV2 { @HttpGet global static AccountWrapperV2 getAccount() { // Include additional fields and relationships RestRequest req = RestContext.request; String accountId = req.requestURI.substringAfterLast('/'); // Check for API version header String apiVersion = req.headers.get('API-Version'); if (apiVersion == '1.0') { // Return V1 format for backward compatibility return convertToV1Format(getAccountData(accountId)); } // Return V2 format by default return getAccountData(accountId); } } ```

Security and Authentication Patterns

## OAuth 2.0 Implementation ### Connected App Configuration ```xml <!-- Connected App Settings --> <connectedApp> <contactEmail>admin@company.com</contactEmail> <label>External System Integration</label> <oauthConfig> <callbackUrl>https://external-system.com/oauth/callback</callbackUrl> <scopes> <scope>api</scope> <scope>refresh_token</scope> <scope>offline_access</scope> </scopes> <isSecretRequired>true</isSecretRequired> </oauthConfig> <oauthPolicy> <ipRelaxation>ENFORCE</ipRelaxation> <refreshTokenPolicy>infinite</refreshTokenPolicy> </oauthPolicy> </connectedApp> ``` ### JWT Bearer Flow Implementation ```apex public class JWTBearerFlow { public static String getAccessToken() { // JWT Header Map<String, Object> header = new Map<String, Object>{ 'alg' => 'RS256', 'typ' => 'JWT' }; // JWT Claims Map<String, Object> claims = new Map<String, Object>{ 'iss' => '3MVG9...', // Consumer Key 'sub' => 'integration@company.com', // Username 'aud' => 'https://login.salesforce.com', 'exp' => DateTime.now().addMinutes(5).getTime() / 1000 }; // Generate JWT String jwt = generateJWT(header, claims); // Request access token HttpRequest req = new HttpRequest(); req.setEndpoint('https://login.salesforce.com/services/oauth2/token'); req.setMethod('POST'); req.setBody('grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' + '&assertion=' + jwt); Http http = new Http(); HttpResponse res = http.send(req); if (res.getStatusCode() == 200) { Map<String, Object> response = (Map<String, Object>) JSON.deserializeUntyped(res.getBody()); return (String) response.get('access_token'); } else { throw new AuthException('JWT authentication failed: ' + res.getBody()); } } private static String generateJWT( Map<String, Object> header, Map<String, Object> claims ) { // Implementation details for JWT generation // Typically involves signing with private key return 'generated.jwt.token'; } } ``` ### API Security Best Practices ```apex @RestResource(urlMapping='/api/secure/data/*') global with sharing class SecureDataService { @HttpPost global static ResponseWrapper processData(RequestWrapper request) { try { // 1. Validate authentication if (!validateApiKey()) { return new ResponseWrapper(401, 'Unauthorized'); } // 2. Check rate limits if (!checkRateLimit()) { return new ResponseWrapper(429, 'Rate limit exceeded'); } // 3. Validate input ValidationResult validation = validateInput(request); if (!validation.isValid) { return new ResponseWrapper(400, validation.errors); } // 4. Check permissions if (!hasPermission(request.operation)) { return new ResponseWrapper(403, 'Forbidden'); } // 5. Process request with encryption String encryptedData = Crypto.encryptWithManagedIV( 'AES256', Blob.valueOf(getEncryptionKey()), Blob.valueOf(JSON.serialize(request.data)) ).toString(); // 6. Audit trail createAuditLog(request, 'Success'); return new ResponseWrapper(200, 'Success', encryptedData); } catch (Exception e) { createAuditLog(request, 'Error: ' + e.getMessage()); return new ResponseWrapper(500, 'Internal server error'); } } private static Boolean validateApiKey() { String apiKey = RestContext.request.headers.get('X-API-Key'); // Validate against Custom Metadata Type API_Key__mdt keyRecord = [ SELECT Id, Is_Active__c, Expiration_Date__c FROM API_Key__mdt WHERE Key__c = :apiKey AND Is_Active__c = true LIMIT 1 ]; return keyRecord != null && (keyRecord.Expiration_Date__c == null || keyRecord.Expiration_Date__c > Date.today()); } private static Boolean checkRateLimit() { String clientId = RestContext.request.headers.get('X-Client-Id'); // Check requests in last hour Integer requestCount = [ SELECT COUNT() FROM API_Request_Log__c WHERE Client_Id__c = :clientId AND CreatedDate = LAST_N_HOURS:1 ]; return requestCount < 1000; // 1000 requests per hour limit } } ```

Error Handling and Monitoring

## Comprehensive Error Handling ### Retry Pattern Implementation ```apex public class IntegrationRetryHandler implements Queueable, Database.AllowsCallouts { private Integration_Queue__c queueItem; private static final Integer MAX_RETRIES = 3; private static final List<Integer> RETRY_INTERVALS = new List<Integer>{ 1, 5, 15 // Minutes }; public IntegrationRetryHandler(Id queueItemId) { this.queueItem = [ SELECT Id, Record_Id__c, Object_Type__c, Operation__c, Payload__c, Retry_Count__c, Status__c FROM Integration_Queue__c WHERE Id = :queueItemId ]; } public void execute(QueueableContext context) { try { // Attempt integration IntegrationResult result = performIntegration(); if (result.isSuccess) { queueItem.Status__c = 'Completed'; queueItem.Completed_Date__c = DateTime.now(); } else { handleFailure(result); } update queueItem; } catch (Exception e) { handleException(e); } } private void handleFailure(IntegrationResult result) { queueItem.Last_Error__c = result.errorMessage; queueItem.Retry_Count__c = queueItem.Retry_Count__c + 1; if (queueItem.Retry_Count__c < MAX_RETRIES) { // Schedule retry Integer delayMinutes = RETRY_INTERVALS[Integer.valueOf(queueItem.Retry_Count__c) - 1]; queueItem.Status__c = 'Retry Scheduled'; queueItem.Next_Retry__c = DateTime.now().addMinutes(delayMinutes); // Enqueue with delay System.enqueueJob( new IntegrationRetryHandler(queueItem.Id), delayMinutes ); } else { // Max retries reached queueItem.Status__c = 'Failed'; notifyAdministrators(); } } private void handleException(Exception e) { queueItem.Status__c = 'Error'; queueItem.Last_Error__c = e.getTypeName() + ': ' + e.getMessage(); queueItem.Stack_Trace__c = e.getStackTraceString(); update queueItem; // Create platform event for real-time monitoring Integration_Error_Event__e errorEvent = new Integration_Error_Event__e( Queue_Item_Id__c = queueItem.Id, Error_Message__c = e.getMessage(), Stack_Trace__c = e.getStackTraceString(), Severity__c = 'High' ); EventBus.publish(errorEvent); } } ``` ### Integration Monitoring Dashboard ```apex public class IntegrationMonitoringService { @AuraEnabled(cacheable=true) public static IntegrationMetrics getIntegrationMetrics() { IntegrationMetrics metrics = new IntegrationMetrics(); // Last 24 hours statistics DateTime last24Hours = DateTime.now().addHours(-24); // Success rate AggregateResult[] results = [ SELECT Status__c, COUNT(Id) cnt FROM Integration_Queue__c WHERE CreatedDate >= :last24Hours GROUP BY Status__c ]; for (AggregateResult ar : results) { String status = (String) ar.get('Status__c'); Integer count = (Integer) ar.get('cnt'); if (status == 'Completed') { metrics.successCount = count; } else if (status == 'Failed') { metrics.failureCount = count; } else if (status == 'Retry Scheduled' || status == 'Pending') { metrics.pendingCount = count; } } // Average response time AggregateResult avgResult = [ SELECT AVG(Response_Time__c) avgTime FROM Integration_Log__c WHERE CreatedDate >= :last24Hours AND Status__c = 'Success' ]; metrics.averageResponseTime = avgResult.get('avgTime') != null ? (Decimal) avgResult.get('avgTime') : 0; // Error trends metrics.errorTrends = getErrorTrends(); return metrics; } public class IntegrationMetrics { @AuraEnabled public Integer successCount { get; set; } @AuraEnabled public Integer failureCount { get; set; } @AuraEnabled public Integer pendingCount { get; set; } @AuraEnabled public Decimal averageResponseTime { get; set; } @AuraEnabled public List<ErrorTrend> errorTrends { get; set; } public IntegrationMetrics() { this.successCount = 0; this.failureCount = 0; this.pendingCount = 0; this.averageResponseTime = 0; this.errorTrends = new List<ErrorTrend>(); } } } ```

Certified Partner

Salesforce certified consultants

5-Star Rated

Consistently high client satisfaction

200+ Projects

Successfully delivered

Enterprise Ready

Fortune 500 trusted

Ready to Transform Your Business?

Get expert Salesforce consulting tailored to your needs. Schedule a free consultation to discuss your project.

Free consultation • No obligation • Expert advice