Example: Publish Callback Class That Correlates Callback Results with Event Messages

This example callback class implementation shows how to retry publishing failed events. It’s based on a trigger on the Order object.

Callback Class 

If event publishing fails, the onFailure method in the FailureCallbackWithCorrelation class is invoked. This method retries publishing failed events up to two times. A map holds the UUID values of each published event and maps it to the order record ID. This mapping is used to populate the event Order_Id__c field. Alternatively, you can use the record ID to obtain field data from the record and populate event fields. The example omits this detail for simplicity.

The examples in this section require a platform event, Order Event, to be defined with a Text(18) field of Order Id.

1public class FailureCallbackWithCorrelation implements EventBus.EventPublishFailureCallback
2{
3    public static final Integer MAX_RETRIES = 2;
4    private Integer retryCounter = 0;
5    private Map<String,String> uuidMap;
6    
7    // Callback constructor
8    public FailureCallbackWithCorrelation(Map<String,String> uuidMap) {
9        this.uuidMap = uuidMap;
10    }
11    
12    public void onFailure(EventBus.FailureResult result) {
13        List<String> eventUuids = result.getEventUuids();
14        Map<String,String> newUuidMap = new Map<String,String>();
15        
16        if (retryCounter < MAX_RETRIES) {
17            // Try to re-publish the failed events
18            List<Order_Event__e> events = new List<Order_Event__e>();
19            for (String uuid : eventUuids) {
20                // Create a new event with the contents of the failed event
21                Order_Event__e event = (Order_Event__e) 
22                    Order_Event__e.sObjectType.newSObject(null, true);
23                // Fill event with the right order record Id
24                event.Order_Id__c = uuidMap.get(uuid);  
25                events.add(event);
26                
27                // Use a new map since the new event will have a different uuid
28                newUuidMap.put(event.EventUuid, event.Order_Id__c);
29            }
30            // Replace old uuid map because we no longer need its contents
31            uuidMap = newUuidMap;
32            
33            // Republish with the same callback passed in again as 'this'
34            System.debug('Republish ' + eventUuids.size() + ' failed events.');
35            EventBus.publish(events, this); 
36            System.debug('Republish event for Order with Ids: ' + 
37                         String.join(uuidMap.values(), ', '));
38            
39            // Increase counter
40            retryCounter++;
41        } else {
42            // Retry exhausted, log an error instead
43            System.debug(eventUuids.size() + ' event(s) failed to publish after ' + 
44                         MAX_RETRIES + ' retries ' +
45                         'for Order with Ids: ' + String.join(uuidMap.values(), ', '));
46        }
47    }
48    
49    // Getter methods so we can validate this in the unit test
50    public Integer getRetryCounter() {
51        return retryCounter;
52    }
53
54    public Map<String,String> getUuidMap() {
55        return uuidMap;
56    }
57}

Apex Trigger 

For each inserted or updated order record, the trigger publishes the Order_Event__e platform event with a populated EventUuid field.

1trigger OrderTrigger on Order (after insert, after update) {
2    Map<String,String> uuidMap = new Map<String,String>();
3    List<Order_Event__e> events = new List<Order_Event__e>();
4    
5    for (Order o : Trigger.new) {
6        Order_Event__e e = (Order_Event__e) 
7        Order_Event__e.sObjectType.newSObject(null, true);
8        // Fill event field with Order Id
9        e.Order_Id__c = o.Id; 
10        // Map event UUID -> Order Id so we can look up later
11        uuidMap.put(e.EventUuid, o.Id); 
12        events.add(e);
13    }
14
15    FailureCallbackWithCorrelation cb = new FailureCallbackWithCorrelation(uuidMap);
16    List<Database.SaveResult> srs = EventBus.publish(events, cb);
17
18    // Inspect immediate publish result
19    for (Database.SaveResult sr : srs) {
20        if (sr.isSuccess()) {
21            System.debug('Successfully enqueued event.');
22        } else {
23            for(Database.Error err : sr.getErrors()) {
24                System.debug('Error returned: ' + err.getStatusCode() + ' - ' + 
25                    err.getMessage());
26            }
27        }
28    }
29}

To run the trigger, insert an order record. Because an order depends on an account and contract, create these records first. You can create the records in the user interface or via Apex or an API. An Apex snippet is provided for your convenience. You can run this snippet in the Developer Console, in the Execute Anonymous Window.

1// Create account
2Account a = new Account();
3a.Name = 'Account Callback';
4insert a;
5
6// Create contract
7Contract c = new Contract();
8c.StartDate = Date.today();
9c.ContractTerm = 12;
10c.Status = 'Draft';
11c.AccountId = a.Id;
12insert c;
13
14// Create order
15Order o = new Order();
16o.AccountId = a.Id;
17o.ContractId = c.Id;
18o.Status = 'Draft';
19o.EffectiveDate = Date.today();
20insert o;