Apex Integration Best Practices: Building Robust Salesforce Integrations
Integrating Salesforce with external systems can be a game-changer for streamlining business processes, ensuring data consistency, and extending your org’s capabilities. However, such integrations also raise concerns about performance, security, and reliability. By following certain best practices in Apex integration, you ensure your solution remains robust and scalable.
1. Use Named Credentials for Secure Callouts
Why: Instead of storing credentials or tokens in Apex code, you can define an endpoint and authentication (e.g., OAuth 2.0, Basic Auth) in Named Credentials. Apex then references it with a simple callout: prefix, keeping your code environment-agnostic and avoiding hardcoded secrets.
public class NamedCredentialService {
public static String callExternalService() {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_External_Service/resource/path');
req.setMethod('GET');
req.setHeader('Content-Type','application/json');
// Named Credential handles authentication automatically
Http http = new Http();
HttpResponse res = http.send(req);
}
}
Best Practices:
2. Bulkify and Asynchronize Callouts
Why: Large data volumes or time-intensive external calls can degrade user experience if executed synchronously. Use Queueable, Future methods, or Batch Apex to offload heavier tasks.
public class BulkQueueableCallout implements Queueable, Database.AllowsCallouts {
private List<Id> recordIds;
public BulkQueueableCallout(List<Id> recordIds) {
this.recordIds = recordIds;
}
public void execute(QueueableContext context) {
List<MyObject__c> records = [
SELECT Id, Name, External_Ref__c
FROM MyObject__c
WHERE Id IN :recordIds
];
// Example logic: Call out for each record's External_Ref__c
for (MyObject__c rec : records) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/resource/' + rec.External_Ref__c);
req.setMethod('GET');
HttpResponse resp = (new Http()).send(req);
// Process response or update fields
}
}
}
Best Practices:
3. Manage Timeouts and Retries
Why: Network hiccups and external service downtimes are inevitable. The default timeout might not always suffice or might be too lenient.
public class TimeoutRetryService {
public static String doCalloutWithRetry() {
Integer maxAttempts = 3;
Integer attempt = 0;
String finalResponse;
while (attempt < maxAttempts) {
attempt++;
try {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ExternalService');
req.setMethod('POST');
req.setTimeout(15000); // 15 seconds
req.setBody('{"data":"test"}');
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
finalResponse = res.getBody();
break;
} else {
// Could log or throw a custom exception or set custom final response
}
} catch (Exception e) {
// If we're on the final attempt, rethrow
if (attempt == maxAttempts) {
throw e;
}
}
}
return finalResponse;
}
}
Retry Logic:
4. JSON Parsing and Error Handling
Why: Many REST APIs return JSON. Apex supports JSON.deserialize() robust parsing, but you must handle partial or erroneous payloads gracefully.
public class JsonParserHelper {
public class ExternalResponse {
public String status;
public String message;
public Decimal amount;
}
public static ExternalResponse parseJson(String jsonBody) {
try {
ExternalResponse parsed = (ExternalResponse) JSON.deserialize(jsonBody, ExternalResponse.class);
return parsed;
} catch (Exception e) {
System.debug('JSON Parsing Error: ' + e.getMessage());
return null;
}
}
public static void handleApiResponse(String jsonBody) {
ExternalResponse res = parseJson(jsonBody);
if (res == null) {
// Possibly throw a custom exception or log
} else if (res.status == 'Error') {
// Log, rethrow, or handle gracefully
} else {
// Proceed with normal logic
}
}
}
Best Practices:
5. Handle Security and Compliance
Key Points:
6. Performance and Governor Limits
Minimize Round Trips:
Timeout Management:
Avoid Over-Triggering:
Use Queues:
7. Testing and Mocking
Why: You want reliable, repeatable tests that don’t rely on an actual external service. Approach: Use HttpCalloutMock to return synthetic responses:
@IsTest
private class MyIntegrationTest {
@IsTest
static void testCalloutMock() {
Test.setMock(HttpCalloutMock.class, new MyHttpMock());
String response = NamedCredentialService.callExternalService();
System.assertEquals('Mock Response', response, 'Unexpected response body');
}
}
private class MyHttpMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('Mock Response');
return res;
}
}
Mocking:
Conclusion
Apex integration is potent for connecting Salesforce to external APIs but calls for robust design around security, performance, and error handling. By leveraging Named Credentials, asynchronous patterns, retries, and mock testing, you’ll ensure your integration is both resilient and easy to maintain as your org evolves.
Key Takeaways:
Salesforce Technical Architect | Lead Developer | SME | AI Enthusiast | Enterprise Cloud Transformation | Salesforce Mentor
4moDeepankar Tiwari Good . you covered all scenarios. For better logging we can use "Nebula logger" . and inbound updates ,we can use composite API to mitigate DML count.
3X Salesforce Certified | 2X Microsoft Certified |Salesforce Developer | Salesforce Administrator | Salesforce Testing | Selenium Automation | Java | C# | SQL |Agile| MS-PowerBI | MS-SQL Server
4moRetry logic is what contributes to Data Integrity and Essential for Integration. Very insightful 👍