The five failure modes
After 200+ rescue engagements, the same five patterns keep showing up. Each one has a technical signature you can spot in a code review.
1. Custom object sprawl
Signature: more than 25 custom objects created in year one, half of them ending in _Log__c or _Wrapper__c.
Fix: map every proposed object to a Common Component before building. Account + Contact + Opportunity + Case cover 70% of enterprise use cases when combined with Record Types.
2. Trigger anarchy
Signature: 6 triggers on the same object, mixed with 4 Record-Triggered Flows and 2 Process Builders. Order of execution is undefined.
Fix: one trigger per object, framework-based. Trigger Handler pattern:
public class OpportunityTriggerHandler extends TriggerHandler {
public override void beforeInsert() { OpportunityService.stampSegment(Trigger.new); }
public override void afterUpdate() { OpportunityService.rollupToAccount((Map<Id,Opportunity>)Trigger.newMap); }
}
trigger OpportunityTrigger on Opportunity (before insert, after update) {
new OpportunityTriggerHandler().run();
}
Turn off all Process Builders. Convert the ones you need to keep into Record-Triggered Flows with clearly ordered priorities.
3. Governor limit surprises in year two
Signature: it worked in UAT with 1,000 records. It times out in production with 500,000.
Fix: design for bulk from day one.
// ❌ Governor-limit trap
for (Account a : accounts) {
List<Opportunity> opps = [SELECT Id FROM Opportunity WHERE AccountId = :a.Id];
...
}
// ✅ One query, in-memory map
Map<Id, List<Opportunity>> byAccount = new Map<Id, List<Opportunity>>();
for (Opportunity o : [SELECT Id, AccountId FROM Opportunity WHERE AccountId IN :accIds]) {
byAccount.putIfAbsent(o.AccountId, new List<Opportunity>()).add(o);
}
Load-test with the Apex Governor Limits Analyzer and Big Object samples before go-live.
4. Sharing model discovered too late
Signature: week before go-live, someone realizes reps can see every opportunity in the org.
Fix: decide OWD, roles, and sharing rules in week one, not month six. Document the decision:
Object OWD Sharing mechanism
Account Private Role hierarchy + Account Teams
Opportunity Controlled Inherits from Account
Case Private Queues + criteria-based rules
Custom_Deal__c Private Apex managed sharing (complex hierarchy)
Every custom object gets a sharing decision on the same PR that creates the SObject.
5. No environment strategy
Signature: all changes go through one "Full Copy" sandbox and get click-deployed on Friday nights.
Fix: three-tier + source of truth in Git.
Developer scratch orgs
▼ (feature branch → PR)
Integration sandbox (Copado / Gearset auto-deploy on merge)
▼ (release branch)
UAT / Full sandbox (weekly deploy, automated regression via Provar/UTAM)
▼ (tag)
Production (blue/green via unlocked packages)
The architecture review checklist
Before signing off a Salesforce build, verify:
- All triggers use one handler per object
- Zero active Process Builders
- Every SOQL inside a loop is a bug — CI fails the build
- Sharing model documented per object, tested with
runAs() - Metadata in Git with a PR review policy
- Sandbox refresh policy defined
- Data archival strategy in place before hitting 10M records per object
- Field-Level Security audit run against every profile & permission set
Cost of skipping this
Rescue engagements average 4–6× the cost of building it right the first time — mostly because business logic must be reverse-engineered from an org no one owns anymore. Invest in architecture before features.
