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