This post demonstrates how to use Salesforce Change Data Capture (CDC) as the mechanism for transmitting change events to an intelligent middleware layer. To see a demo, check out the companion YouTube video.
Motivation
Using Salesforce Change Data Capture (CDC), a middleware layer can subscribe to all of record changes for an particular object (e.g., Order). The business logic for transforming and transmitting the changes to other systems can then be hosted in the middleware, rather that in Salesforce itself. This enables an integration architecture where the custom code running in Salesforce is relatively simple and all the business logic is implemented in the middleware layer itself. Once advantage of this approach is that integration changes can be made without having to modify custom code (e.g., Apex) running on Salesforce.
CDC vs Platform Events
In my previous post, A Simple Middleware Framework using the Salesforce Pub/Sub API and Platform Events, I showed how Salesforce Platform Events can be used to communicate specific changes to a middleware layer. Platform Events let you communicate specific changes to the middleware that require action. In that post, I looked at the example of a order becoming activated. In this case, the middleware needs to handle the order activation by triggering an invoicing system (for example) to generate an invoice. In this case, the business logic of identifying a change requiring action (i.e., order activation) resides within Salesforce.
One drawback of this approach is that business logic must be maintained in both Salesforce and in the middleware layer. As requirements change, Salesforce code (or Flows) must be updated. This requires a change, test, deploy cycle in Salesforce as well as in the middleware.
Alternatively, if CDC is used to communicate all order changes to the middleware, then you can put the business logic that identifies which changes require action in the middleware itself. This makes the middleware more complex, but it greatly simplifies the customization required in Salesforce. Furthermore, when requirements change, only the middleware needs to go through the change, test, deploy cycle.
In summary, Platform Events are best for custom, business-driven messages where you define the payload, while Change Data Capture (CDC) is for automatically broadcasting record changes for data synchronization. Key differences include: Platform Events are manually published and custom-defined, ideal for distributed architectures needing custom notifications; CDC events are automatically published with pre-defined fields (like old and new values), ideal for real-time data replication. See: Platform Events vs. Change Data Capture — Which One Should You Use
How to Implement CDC in Salesforce
Implementing CDC in Salesforce is very simple. It is really just a matter of turning it on. Head to Setup > Integrations > Change Data Capture and select the objects that you want. In this example, I’ve selected just the Orders object.
The corresponding sfdx metadata entry is equally simple. Located under force-app/main/default/platformEventChannelMembers directory, the file ChangeEvents_OrderChangeEvent.platformEventChannelMember-meta.xml contains just 4 lines.
ChangeEvents
OrderChangeEvent
Subscribing to CDC Events in Middleware
On the middleware side, I was able to use the same a Pub/Sub client implementation (salesforce-pubsub-api-client) to connect with Salesforce that I use for platform events. The only difference is that the channel to subscribe to is /data/OrderChangeEvent.
pubSubApiClient.subscribe('/data/OrderChangeEvent', this.eventHandler.handleEvents.bind(this.eventHandler), 3);
Any change events that come in on that channel get passed to an event handler. At this stage, the event handler I implemented simply prints out the event JSON so I can have a look at it and decide how to transform it and use it to propagate changes to other systems.
{
"id": "899770c1-545a-4234-9e50-226b5b9cbb11",
"schemaId": "v26kqo-ufkGLw56sjU2BQg",
"replayId": 2451495,
"payload": {
"ChangeEventHeader": {
"entityName": "Order",
"recordIds": [
"801cb00000I9CR8AAN"
],
"changeType": "CREATE",
"changeOrigin": "com/salesforce/api/soap/65.0;client=SfdcInternalAPI/",
"transactionKey": "00001762-2a16-a21d-b01c-cfac95fa5640",
"sequenceNumber": 1,
"commitTimestamp": 1762521066000,
"commitNumber": "1762521066658021377",
"commitUser": "005cb000009KaVFAA0",
"nulledFields": [],
"diffFields": [],
"changedFields": []
},
"OwnerId": "005cb000009KaVFAA0",
"ContractId": "800cb00000D34SbAAJ",
"AccountId": "001cb00000I4n9lAAB",
"Pricebook2Id": null,
"OriginalOrderId": null,
"OpportunityId": null,
"EffectiveDate": 1763337600000,
"EndDate": null,
"IsReductionOrder": false,
"Status": "Draft",
"Description": "Fabulous foobot widget.",
"CustomerAuthorizedById": null,
"CustomerAuthorizedDate": null,
"CompanyAuthorizedById": null,
"CompanyAuthorizedDate": null,
"Type": null,
"BillingAddress": {
"Street": "123 Main St",
"City": "Toledo",
"State": "OH",
"PostalCode": "74511",
"Country": "USA",
"Latitude": null,
"Longitude": null,
"GeocodeAccuracy": null
},
"ShippingAddress": {
"Street": "28 Hampton Road",
"City": "Scarsdale",
"State": "NY",
"PostalCode": "10583",
"Country": "USA",
"Latitude": null,
"Longitude": null,
"GeocodeAccuracy": null
},
"Name": null,
"PoDate": null,
"PoNumber": null,
"OrderReferenceNumber": null,
"BillToContactId": null,
"ShipToContactId": null,
"ActivatedDate": null,
"ActivatedById": null,
"StatusCode": "Draft",
"OrderNumber": "00000101",
"TotalAmount": 0,
"CreatedDate": 1762521066000,
"CreatedById": "005cb000009KaVFAA0",
"LastModifiedDate": 1762521066000,
"LastModifiedById": "005cb000009KaVFAA0"
}
}
In this example, the message describes a new order creation ( “changeType”: “CREATE”). Key information like the BillingAddress, ShippingAddress, and AccountId are included. If I were integrating with an invoicing system, I could make a REST API call to Salesforce to pull in the Account details (using AccountId) to get any additional account information needed to create the invoice.
As you can see, the channel provides a lot of data, and we get notified of every create, update, and delete. The burden of figuring out which events need to be handled, and which can be ignored, falls to the business logic encoded in the middleware. On the plus side, all the business logic is consolidated in one place and not split between Salesforce and the middleware layer as happens when using Platform Events.
For a demo showing more details about how to code this up and run it, check out the companion YouTube video.