mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 11:19:27 +08:00
* Migrating Core Salesforce Skills * Updating pr comments * updat reference * Updating a skill * Migrating Datacloud skills * Migrating Industries cloud skills * Validating - skills fixing --------- Co-authored-by: Sandip Kumar Yadav <sandipkumar.yadav+sfemu@salesforce.com>
8.2 KiB
8.2 KiB
Change Data Capture (CDC) Guide
Overview
Change Data Capture publishes change events for Salesforce records, enabling near real-time data synchronization with external systems.
How CDC Works
┌─────────────────────────────────────────────────────────────────┐
│ CHANGE DATA CAPTURE FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Record Created/Updated/Deleted in Salesforce │
│ ↓ │
│ 2. Salesforce generates Change Event │
│ ↓ │
│ 3. Event published to {{Object}}ChangeEvent channel │
│ ↓ │
│ 4. Apex Trigger or External Subscriber receives event │
│ ↓ │
│ 5. Process event (sync, audit, notify) │
│ │
└─────────────────────────────────────────────────────────────────┘
CDC vs Platform Events
| Feature | CDC | Platform Events |
|---|---|---|
| Trigger | Automatic on DML | Manual publish |
| Schema | Predefined (object fields) | Custom defined |
| Use Case | Data sync/replication | Custom messaging |
| Control | Limited | Full control |
Enabling CDC
Via Setup
- Go to Setup → Integrations → Change Data Capture
- Select objects to enable
- Save
Supported Objects
- Most Standard objects (Account, Contact, Opportunity, etc.)
- Custom objects
- Some system objects (User, Group, etc.)
Event Channel Names
| Object Type | Channel Name |
|---|---|
| Standard | AccountChangeEvent, ContactChangeEvent |
| Custom | My_Object__ChangeEvent (append ChangeEvent) |
Change Event Structure
ChangeEventHeader
EventBus.ChangeEventHeader header = event.ChangeEventHeader;
// Available methods
header.getChangeType(); // CREATE, UPDATE, DELETE, UNDELETE
header.getChangedFields(); // List of changed field API names
header.getRecordIds(); // Affected record IDs
header.getEntityName(); // Object API name
header.getCommitNumber(); // Transaction sequence
header.getCommitTimestamp(); // When change occurred
header.getTransactionKey(); // Unique transaction ID
Change Types
| Type | Description | Event Contains |
|---|---|---|
| CREATE | New record | All field values |
| UPDATE | Record modified | Changed field values only |
| DELETE | Record deleted | Record IDs only |
| UNDELETE | Restored from bin | All field values |
| GAP_* | Events missed | Affected record IDs |
| GAP_OVERFLOW | Too many changes | Entity name |
Field Values
- Changed fields: Contain new values
- Unchanged fields: Null
- Use
getChangedFields()to know what changed
Subscribing to CDC
Apex Trigger
trigger AccountCDC on AccountChangeEvent (after insert) {
for (AccountChangeEvent event : Trigger.new) {
EventBus.ChangeEventHeader header = event.ChangeEventHeader;
String changeType = header.getChangeType();
List<String> changedFields = header.getChangedFields();
List<String> recordIds = header.getRecordIds();
switch on changeType {
when 'CREATE' {
// Handle new records
}
when 'UPDATE' {
// Handle updates
}
when 'DELETE' {
// Handle deletions
}
}
}
// Set checkpoint
EventBus.TriggerContext.currentContext().setResumeCheckpoint(
Trigger.new[Trigger.new.size()-1].ReplayId
);
}
External (CometD)
Channel: /data/AccountChangeEvent
Handling Specific Changes
Filter by Changed Fields
// Only process if important fields changed
Set<String> importantFields = new Set<String>{'Status__c', 'Amount__c'};
List<String> changedFields = header.getChangedFields();
Boolean relevant = false;
for (String field : changedFields) {
if (importantFields.contains(field)) {
relevant = true;
break;
}
}
if (!relevant) return;
Get New Values
// Access field values from event (UPDATE: only changed fields have values)
if (changedFields.contains('Status__c')) {
String newStatus = event.Status__c;
// Process status change
}
Gap Events
Gap events indicate missed events:
Types
| Event | Meaning | Action |
|---|---|---|
| GAP_CREATE | Missed creates | Query and sync records |
| GAP_UPDATE | Missed updates | Query current state |
| GAP_DELETE | Missed deletes | Reconcile with source |
| GAP_UNDELETE | Missed restores | Query and sync |
| GAP_OVERFLOW | Too many changes | Full sync required |
Handling Gaps
when 'GAP_CREATE', 'GAP_UPDATE', 'GAP_DELETE', 'GAP_UNDELETE' {
// Query current state and sync
List<Account> records = [
SELECT Id, Name, Status__c
FROM Account
WHERE Id IN :header.getRecordIds()
];
syncToExternalSystem(records);
}
when 'GAP_OVERFLOW' {
// Trigger full sync batch job
Database.executeBatch(new FullSyncBatch());
}
Replay and Durability
Retention
CDC events retained for 3 days (72 hours).
Replay ID
Each event has unique ReplayId for tracking:
String replayId = event.ReplayId;
Resume Checkpoint
Critical for durability:
// Always set checkpoint at end of trigger
EventBus.TriggerContext.currentContext().setResumeCheckpoint(lastReplayId);
If trigger fails after checkpoint, processing resumes from that point.
External Sync Pattern
public class AccountCDCHandler {
public static void syncToExternal(AccountChangeEvent event) {
EventBus.ChangeEventHeader header = event.ChangeEventHeader;
Map<String, Object> payload = new Map<String, Object>{
'recordIds' => header.getRecordIds(),
'operation' => header.getChangeType(),
'timestamp' => header.getCommitTimestamp(),
'changedFields' => header.getChangedFields()
};
// Add field values for CREATE/UPDATE
if (header.getChangeType() != 'DELETE') {
payload.put('name', event.Name);
payload.put('accountNumber', event.AccountNumber);
// Add relevant fields
}
// Queue async callout
System.enqueueJob(new ExternalSyncJob(payload));
}
}
Best Practices
DO
- Set resume checkpoint in every trigger
- Filter by relevant fields to reduce noise
- Handle all change types including GAPs
- Process idempotently (events may replay)
- Use async for external callouts
- Log changes for debugging
DON'T
- Don't throw exceptions - catch and log
- Don't ignore GAP events - they indicate data loss
- Don't assume single record - batch DML creates multi-record events
- Don't block on external calls - use Queueable
Monitoring
Event Delivery
Check for failures in:
- Setup → Platform Events → Monitor
- Debug logs for triggers
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Missing events | No CDC enabled | Enable in Setup |
| Duplicate processing | No idempotency | Check transactionKey |
| GAP events | Processing too slow | Optimize trigger, scale out |
| Timeout | Heavy processing | Move to async |
Limits
| Limit | Value |
|---|---|
| Objects per org | 100 |
| Events per 15 minutes | Varies by edition |
| Event retention | 3 days (72 hours) |
| Replay window | 3 days |