Enterprise Integration Patterns & Middleware
📖 Concept
Enterprise Salesforce implementations rarely exist in isolation — they integrate with ERP (SAP, Oracle), HRIS (Workday), marketing (Marketo, HubSpot), and custom systems. Understanding integration patterns is an architect-level competency.
Core Integration Patterns:
Request-Reply (Synchronous)
- Salesforce calls external system and waits for response
- Use for: Real-time data lookup, address validation, credit check
- Limitation: 120-second timeout, blocks the user
Fire-and-Forget (Asynchronous)
- Salesforce sends data and doesn't wait for response
- Use for: Logging, notifications, non-critical syncs
- Implement via: @future, Queueable, Platform Events
Batch Data Sync
- Scheduled bulk data transfer between systems
- Use for: Nightly data warehouse sync, mass updates
- Implement via: Batch Apex + callouts, Bulk API
Event-Driven (Pub/Sub)
- Systems react to events published by other systems
- Use for: Real-time sync, change notification, decoupled architecture
- Implement via: Platform Events, Change Data Capture, webhooks
Data Virtualization
- Access external data without importing it
- Use for: Real-time external data access, large datasets
- Implement via: Salesforce Connect (OData), External Objects
Middleware considerations:
- MuleSoft — Salesforce's preferred middleware (Salesforce owns MuleSoft)
- Benefits: API management, data transformation, error handling, monitoring
- When to use middleware: Multiple systems (3+), complex transformations, need for guaranteed delivery, regulatory compliance
Integration Architecture Decision Framework:
Volume? → High → Bulk API / Batch Apex
Timing? → Real-time → Request-Reply / Events
Direction? → Inbound → REST API / Platform Events
Outbound → Callouts / @future
Complexity? → High → Middleware (MuleSoft)
Low → Direct point-to-point
💻 Code Example
1// Enterprise Integration Patterns23// 1. Request-Reply Pattern (Synchronous)4public class AddressValidationService {56 public static AddressResult validateAddress(String street, String city, String state) {7 HttpRequest req = new HttpRequest();8 req.setEndpoint('callout:Address_Validator/validate');9 req.setMethod('POST');10 req.setHeader('Content-Type', 'application/json');11 req.setBody(JSON.serialize(new Map<String, String>{12 'street' => street,13 'city' => city,14 'state' => state15 }));16 req.setTimeout(10000); // 10-second timeout for real-time1718 HttpResponse res = new Http().send(req);1920 if (res.getStatusCode() == 200) {21 return (AddressResult) JSON.deserialize(res.getBody(), AddressResult.class);22 }23 throw new CalloutException('Address validation failed');24 }2526 public class AddressResult {27 public Boolean isValid;28 public String standardizedAddress;29 public String zipCode;30 }31}3233// 2. Fire-and-Forget Pattern (Async)34public class ErpSyncService {3536 @future(callout=true)37 public static void syncOrderToErp(Set<Id> orderIds) {38 List<Order> orders = [39 SELECT Id, OrderNumber, TotalAmount, Account.Name40 FROM Order WHERE Id IN :orderIds41 ];4243 for (Order ord : orders) {44 try {45 HttpRequest req = new HttpRequest();46 req.setEndpoint('callout:ERP_System/api/orders');47 req.setMethod('POST');48 req.setHeader('Content-Type', 'application/json');49 req.setBody(JSON.serialize(new Map<String, Object>{50 'orderNumber' => ord.OrderNumber,51 'amount' => ord.TotalAmount,52 'customer' => ord.Account.Name53 }));5455 HttpResponse res = new Http().send(req);56 logResult(ord.Id, res);57 } catch (Exception e) {58 logError(ord.Id, e);59 }60 }61 }6263 private static void logResult(Id orderId, HttpResponse res) {64 insert new Integration_Log__c(65 Record_Id__c = orderId,66 Status__c = res.getStatusCode() < 300 ? 'Success' : 'Failed',67 Response_Code__c = res.getStatusCode(),68 Response_Body__c = res.getBody()?.left(131072)69 );70 }7172 private static void logError(Id orderId, Exception e) {73 insert new Integration_Log__c(74 Record_Id__c = orderId,75 Status__c = 'Error',76 Error_Message__c = e.getMessage()77 );78 }79}8081// 3. Batch Data Sync Pattern82public class NightlyDataSyncBatch implements83 Database.Batchable<SObject>, Database.AllowsCallouts, Database.Stateful {8485 private Integer successCount = 0;86 private Integer failCount = 0;8788 public Database.QueryLocator start(Database.BatchableContext bc) {89 return Database.getQueryLocator([90 SELECT Id, Name, Industry, AnnualRevenue,91 External_Id__c, Last_Sync__c92 FROM Account93 WHERE LastModifiedDate > :getLastSyncTime()94 ]);95 }9697 public void execute(Database.BatchableContext bc, List<Account> scope) {98 // Transform to external format99 List<Map<String, Object>> externalRecords = new List<Map<String, Object>>();100 for (Account acc : scope) {101 externalRecords.add(new Map<String, Object>{102 'sfId' => acc.Id,103 'name' => acc.Name,104 'industry' => acc.Industry,105 'revenue' => acc.AnnualRevenue106 });107 }108109 // Send batch to external system110 HttpRequest req = new HttpRequest();111 req.setEndpoint('callout:Data_Warehouse/api/bulk/customers');112 req.setMethod('POST');113 req.setHeader('Content-Type', 'application/json');114 req.setBody(JSON.serialize(externalRecords));115 req.setTimeout(120000);116117 try {118 HttpResponse res = new Http().send(req);119 if (res.getStatusCode() == 200) {120 successCount += scope.size();121 // Update sync timestamp122 for (Account acc : scope) {123 acc.Last_Sync__c = Datetime.now();124 }125 update scope;126 } else {127 failCount += scope.size();128 }129 } catch (Exception e) {130 failCount += scope.size();131 }132 }133134 public void finish(Database.BatchableContext bc) {135 // Send summary notification136 System.debug('Sync complete. Success: ' + successCount + ', Failed: ' + failCount);137 }138139 private Datetime getLastSyncTime() {140 List<Integration_Config__c> configs = [141 SELECT Last_Sync_Time__c FROM Integration_Config__c142 WHERE Name = 'NightlySync' LIMIT 1143 ];144 return configs.isEmpty() ? Datetime.now().addDays(-1) : configs[0].Last_Sync_Time__c;145 }146}147148// 4. Event-Driven Pattern (Pub/Sub)149// See Phase 3b for Platform Events implementation150151// 5. Retry pattern with exponential backoff152public class RetryableCallout {153154 public static HttpResponse callWithRetry(155 HttpRequest req, Integer maxRetries156 ) {157 Integer retryDelay = 1000; // Start with 1 second158159 for (Integer attempt = 0; attempt <= maxRetries; attempt++) {160 try {161 HttpResponse res = new Http().send(req);162163 if (res.getStatusCode() < 500) {164 return res; // Success or client error — don't retry165 }166167 // Server error — retry with backoff168 if (attempt < maxRetries) {169 // Note: Apex doesn't have sleep(). In real implementation,170 // use Queueable chaining for delayed retry171 System.debug('Retry attempt ' + (attempt + 1) +172 ' after ' + retryDelay + 'ms');173 }174 } catch (CalloutException e) {175 if (attempt == maxRetries) throw e;176 }177 retryDelay *= 2; // Exponential backoff178 }179 throw new CalloutException('Max retries exceeded');180 }181}
🏋️ Practice Exercise
Integration Pattern Practice:
- Implement a Request-Reply pattern for real-time address validation
- Build a Fire-and-Forget sync from Opportunities to an external CRM
- Create a Batch job that syncs changed records nightly to a data warehouse
- Design an event-driven integration using Platform Events and an external subscriber
- Implement a retry mechanism with exponential backoff for failed callouts
- Create an Integration_Log custom object and build logging for all callout operations
- Design a multi-system integration architecture diagram (Salesforce + ERP + HRIS)
- Implement a circuit breaker pattern that stops calling a failing API after 5 failures
- Build a webhook receiver — external system calls your custom REST API when data changes
- Design failure handling: dead letter queue, alerting, manual retry interface
⚠️ Common Mistakes
Using synchronous callouts for non-critical operations — blocks the user. Use async (Platform Events, Queueable) for fire-and-forget scenarios
Not implementing retry logic — network failures are inevitable. Always design for retry with backoff
Point-to-point integration for 5+ systems — creates a spaghetti architecture. Use middleware (MuleSoft) for complex integrations
Not logging integration operations — without logs, debugging production integration failures is nearly impossible
Ignoring rate limits on external APIs — sending 10,000 requests per minute to an API with a 100/min limit causes cascading failures
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Enterprise Integration Patterns & Middleware. Login to unlock this feature.