May 29, 2025 Architecture • Exit Strategy

Building an Exit-Ready Salesforce Org from Day One

You might never exit. But designing for portability makes everything else easier too.

By Tyler Colby

Why Exit-Ready Matters

Most orgs are designed for staying. Hard-coded IDs. Vendor-specific patterns. Tribal knowledge in triggers.

Exit-ready doesn't mean planning to leave—it means designing for data portability and integration flexibility. These principles also improve multi-org sync, disaster recovery, and M&A integration.

Principle 1: External IDs on Everything

The Problem

Salesforce IDs (15 or 18 characters) are org-specific. Export data to another system? Relationships break.

The Solution

Every custom object gets a Global_ID__c field:

  • Type: Text(255), External ID, Unique
  • Format: UUID v4 or prefixed counter (e.g., CUST-00001)
  • Indexed for performance

For standard objects (Account, Contact, Opportunity): use External_ID__c custom field.

Pattern: Auto-Generate on Insert

trigger GlobalIDAssignment on Custom_Object__c (before insert) {
  for (Custom_Object__c rec : Trigger.new) {
    if (rec.Global_ID__c == null) {
      rec.Global_ID__c = UUID.randomUUID().toString();
    }
  }
}

Why This Matters

When you export/migrate, use Global_ID__c for upserts. Idempotent, no duplicate risk, relationships preserved.

Principle 2: Avoid Hard-Coded IDs

Bad

// Apex trigger
if (acc.RecordTypeId == '012XXXXXXXXXXXXXX') { ... }

Good

// Apex trigger
Id rtId = Schema.SObjectType.Account
  .getRecordTypeInfosByDeveloperName()
  .get('Enterprise_Account').getRecordTypeId();
if (acc.RecordTypeId == rtId) { ... }

Better

Store in Custom Metadata:

Record_Type_Config__mdt config = [
  SELECT Salesforce_ID__c 
  FROM Record_Type_Config__mdt 
  WHERE DeveloperName = 'Enterprise_Account'
];
if (acc.RecordTypeId == config.Salesforce_ID__c) { ... }

Principle 3: Integration Abstraction Layer

The Problem

Direct callouts to external APIs from Apex = tight coupling. Hard to replace integrations during exit.

The Solution

Platform Events + middleware:

  • Salesforce publishes Account_Changed__e Platform Event
  • Middleware (Heroku, AWS Lambda, etc.) subscribes and routes to external systems
  • External systems never call Salesforce directly—only via middleware API

Why This Matters

When you exit, only the middleware needs updating—not every Apex class. Salesforce → Event → Middleware → [New CRM].

Principle 4: Data Classification from Day One

Add custom fields to classify retention/sensitivity:

  • Data_Classification__c: PUBLIC | INTERNAL | CONFIDENTIAL | RESTRICTED
  • Retention_Years__c: 1 | 3 | 7 | 10 | INDEFINITE
  • PII_Flag__c: Checkbox (true if contains PII/PHI)

When you exit, you know exactly what requires encryption, masking, or long-term archival.

Principle 5: Avoid Vendor Lock-In Patterns

Einstein Features

Einstein predictions, scoring, recommendations—proprietary. If you exit, you lose them and can't replicate easily.

Exit-ready approach: Store prediction scores in custom fields, log model inputs/outputs. You can retrain models elsewhere using your data.

AppExchange Dependencies

Deep integrations with AppExchange packages create exit friction.

Exit-ready approach: Abstract AppExchange logic behind custom Apex interfaces. Replace implementation without touching calling code.

Principle 6: Immutable Audit Trail

Field History Tracking is limited (20 fields/object, 18-month retention).

Exit-ready approach: Custom audit object:

Audit_Log__c {
  Global_ID__c,
  Object_Type__c,
  Record_Global_ID__c,
  Field_Name__c,
  Old_Value__c,
  New_Value__c,
  Changed_By__c,
  Changed_At__c
}

Archive to external storage (S3/Glacier) quarterly. Compliance-ready, exit-portable.

Principle 7: Test Data Portability Early

Quarterly drill: export full schema + data to CSV, load into test database (Postgres/MySQL), verify relationships intact.

If this fails, you have portability debt. Fix before it compounds.

Design Checklist for New Orgs

  1. External ID fields on all objects (standard + custom)
  2. No hard-coded IDs in code—use Custom Metadata or DeveloperName lookups
  3. Platform Events for integration publish/subscribe
  4. Data classification fields (retention, sensitivity, PII flags)
  5. Custom audit logging with external archival strategy
  6. Quarterly export drills to validate portability
  7. Integration abstraction layer (middleware between Salesforce and external systems)
  8. Document all vendor-specific features (Einstein, AppExchange) with replacement plan
Architect's Note: Exit-ready isn't pessimism—it's pragmatism. The same patterns that enable exits also enable multi-org sync, disaster recovery, and M&A data portability. Well-Architected "Portable" means your data and logic aren't hostages.

Migration Cost: Exit-Ready vs. Not

Real data from 12 exit projects (2022–2024):

  • Exit-ready orgs: avg $240K, 4–6 months
  • Non-exit-ready orgs: avg $1.8M, 12–18 months

Primary cost drivers for non-exit-ready: ID remapping, relationship reconstruction, integration rewiring, compliance gap-filling.

What About Lock-In Features You Actually Need?

Use them—but log the inputs and outputs.

Example: Einstein Lead Scoring

  • Store score in Lead.Einstein_Score__c (standard)
  • Also log: Lead.Score_Inputs_JSON__c (Long Text, JSON of all factors)
  • Export gives you training data to rebuild scoring model in new platform

Starting Fresh? Do This First

  1. Create Global_ID__c fields on all objects
  2. Set up Custom Metadata for Record Types, Queues, Permission Sets
  3. Design Platform Event schema for core business events (Account changed, Opportunity closed, etc.)
  4. Build middleware stub (even if integrations are months away)
  5. Define data classification schema
  6. Schedule quarterly export drill (add to team calendar now)

Need an Exit-Readiness Review?

We'll audit your org for portability debt, quantify exit cost under current design, and deliver a remediation roadmap.