Enterprise Integration Patterns & Middleware

0/3 in this phase0/41 across the roadmap

📖 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:

  1. 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
  2. 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
  3. Batch Data Sync

    • Scheduled bulk data transfer between systems
    • Use for: Nightly data warehouse sync, mass updates
    • Implement via: Batch Apex + callouts, Bulk API
  4. 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
  5. 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

codeTap to expand ⛶
1// Enterprise Integration Patterns
2
3// 1. Request-Reply Pattern (Synchronous)
4public class AddressValidationService {
5
6 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' => state
15 }));
16 req.setTimeout(10000); // 10-second timeout for real-time
17
18 HttpResponse res = new Http().send(req);
19
20 if (res.getStatusCode() == 200) {
21 return (AddressResult) JSON.deserialize(res.getBody(), AddressResult.class);
22 }
23 throw new CalloutException('Address validation failed');
24 }
25
26 public class AddressResult {
27 public Boolean isValid;
28 public String standardizedAddress;
29 public String zipCode;
30 }
31}
32
33// 2. Fire-and-Forget Pattern (Async)
34public class ErpSyncService {
35
36 @future(callout=true)
37 public static void syncOrderToErp(Set<Id> orderIds) {
38 List<Order> orders = [
39 SELECT Id, OrderNumber, TotalAmount, Account.Name
40 FROM Order WHERE Id IN :orderIds
41 ];
42
43 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.Name
53 }));
54
55 HttpResponse res = new Http().send(req);
56 logResult(ord.Id, res);
57 } catch (Exception e) {
58 logError(ord.Id, e);
59 }
60 }
61 }
62
63 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 }
71
72 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}
80
81// 3. Batch Data Sync Pattern
82public class NightlyDataSyncBatch implements
83 Database.Batchable<SObject>, Database.AllowsCallouts, Database.Stateful {
84
85 private Integer successCount = 0;
86 private Integer failCount = 0;
87
88 public Database.QueryLocator start(Database.BatchableContext bc) {
89 return Database.getQueryLocator([
90 SELECT Id, Name, Industry, AnnualRevenue,
91 External_Id__c, Last_Sync__c
92 FROM Account
93 WHERE LastModifiedDate > :getLastSyncTime()
94 ]);
95 }
96
97 public void execute(Database.BatchableContext bc, List<Account> scope) {
98 // Transform to external format
99 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.AnnualRevenue
106 });
107 }
108
109 // Send batch to external system
110 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);
116
117 try {
118 HttpResponse res = new Http().send(req);
119 if (res.getStatusCode() == 200) {
120 successCount += scope.size();
121 // Update sync timestamp
122 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 }
133
134 public void finish(Database.BatchableContext bc) {
135 // Send summary notification
136 System.debug('Sync complete. Success: ' + successCount + ', Failed: ' + failCount);
137 }
138
139 private Datetime getLastSyncTime() {
140 List<Integration_Config__c> configs = [
141 SELECT Last_Sync_Time__c FROM Integration_Config__c
142 WHERE Name = 'NightlySync' LIMIT 1
143 ];
144 return configs.isEmpty() ? Datetime.now().addDays(-1) : configs[0].Last_Sync_Time__c;
145 }
146}
147
148// 4. Event-Driven Pattern (Pub/Sub)
149// See Phase 3b for Platform Events implementation
150
151// 5. Retry pattern with exponential backoff
152public class RetryableCallout {
153
154 public static HttpResponse callWithRetry(
155 HttpRequest req, Integer maxRetries
156 ) {
157 Integer retryDelay = 1000; // Start with 1 second
158
159 for (Integer attempt = 0; attempt <= maxRetries; attempt++) {
160 try {
161 HttpResponse res = new Http().send(req);
162
163 if (res.getStatusCode() < 500) {
164 return res; // Success or client error — don't retry
165 }
166
167 // Server error — retry with backoff
168 if (attempt < maxRetries) {
169 // Note: Apex doesn't have sleep(). In real implementation,
170 // use Queueable chaining for delayed retry
171 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 backoff
178 }
179 throw new CalloutException('Max retries exceeded');
180 }
181}

🏋️ Practice Exercise

Integration Pattern Practice:

  1. Implement a Request-Reply pattern for real-time address validation
  2. Build a Fire-and-Forget sync from Opportunities to an external CRM
  3. Create a Batch job that syncs changed records nightly to a data warehouse
  4. Design an event-driven integration using Platform Events and an external subscriber
  5. Implement a retry mechanism with exponential backoff for failed callouts
  6. Create an Integration_Log custom object and build logging for all callout operations
  7. Design a multi-system integration architecture diagram (Salesforce + ERP + HRIS)
  8. Implement a circuit breaker pattern that stops calling a failing API after 5 failures
  9. Build a webhook receiver — external system calls your custom REST API when data changes
  10. 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.