SRP in Salesforce: Stop Building 'God Classes'
A look at the Single Responsibility Principle and why your Apex classes shouldn't try to do everything at once.
Keep It Simple: Using the Single Responsibility Principle in Salesforce
Ever opened an Apex class only to find it's a 2,000-line "God Class" doing everything from API calls to sending birthday emails? That’s exactly what the Single Responsibility Principle (SRP) helps us avoid.
The idea is simple: A class should have one, and only one reason to change. In the Salesforce world, this makes your code easier to test, harder to break, and much kinder to those pesky governor limits.
Why Bother With SRP?
Building modular code in Apex comes with some serious perks:
- Easier Testing: Small classes mean focused unit tests. No more mocking 10 different dependencies just to test one
ifstatement. - Less "Butterfly Effect": Changing a success email shouldn’t accidentally break your JSON parsing. SRP isolates those risks.
- Governor Limit Friendly: Breaking logic into chunks helps you manage CPU time and SOQL queries more granularly.
The Trade-off? You’ll have more files in your repo. It might feel like "over-engineering" for a tiny project, but your future self (and your teammates) will thank you when the project scales.
Real-World Example: The "Everything" Class (The Wrong Way)
Imagine we're syncing orders from an ERP. Here is a typical "monolith" class that handles the API, the mapping, the database, and the notifications all in one go.
public class OrderSync {
public static void sync() {
// 1. Fetching Data
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('[https://api.erp.com/orders](https://api.erp.com/orders)');
req.setMethod('GET');
try {
HttpResponse res = http.send(req);
// 2. Parsing & Mapping
List<Order__c> toUpsert = new List<Order__c>();
Map<String, Object> root = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
for (Object item : (List<Object>)root.get('orders')) {
Map<String, Object> data = (Map<String, Object>)item;
toUpsert.add(new Order__c(External_Id__c = (String)data.get('id')));
}
upsert toUpsert External_Id__c;
// 3. Emailing (Success)
sendEmail('Sync Complete', 'Everything went great!');
} catch (Exception e) {
// 4. Logging & Emailing (Error)
insert new Error_Log__c(Message__c = e.getMessage());
sendEmail('Sync Failed', e.getMessage());
}
}
private static void sendEmail(String sub, String body) { /* Email Logic */ }
}Why this hurts:
-
Hard to Test: You can’t test the mapping logic without triggering a mock callout and an email.
-
Fragile: If the API format changes, you’re digging through database logic to find the fix.
The "SRP" Way (The Right Way)
Let’s break this down into a team of specialists. Each class does one thing and does it well.
1. The Specialist Classes
// Only handles the API callout
public class OrderClient {
public static String getRawOrders() {
HttpResponse res = new Http().send(new HttpRequest(...));
return res.getBody();
}
}
// Only transforms data
public class OrderMapper {
public static List<Order__c> toSObjects(String jsonStr) {
// Logic to turn JSON into List<Order__c>
}
}
// Only handles notifications
public class Notifier {
public static void notify(String subject, String msg) { /* Messaging logic */ }
}2. The Orchestrator (Controller)
Now, the main class just acts as the "manager," telling the specialists what to do.
public class OrderSyncController {
public static void runSync() {
try {
String rawData = OrderClient.getRawOrders();
List<Order__c> orders = OrderMapper.toSObjects(rawData);
upsert orders External_Id__c;
Notifier.notify('Success', 'Orders Updated.');
} catch (Exception e) {
// Log to DB and notify
insert new Error_Log__c(Message__c = e.getMessage());
Notifier.notify('Error', e.getMessage());
}
}
}Final Thoughts
Adopting SRP in Salesforce means moving away from massive scripts and toward modular services. Yes, it’s more files, but it results in a codebase that is actually "deplorable" without the fear of hitting a CPU timeout or breaking a test class you didn't even know existed.