2

(Sorry for the long description but I promise it will compel you to put on your thinking caps)

We have a scenario wherein we have several platform events to which external systems within my organisation subscribe. For instance, Account object has a platform event object Account__e where all the updates and inserts on accounts are fired and similarly Contact__e and further 3 more for custom objects. Developers of other systems are asking to include a serial/sequence number based on the order they're fired.

To further explain, for eg we have triggers on Account which updates contact records which in turn has trigger to update further more objects, so in this eg scenario there will be two types of events fired one from Account and some from Contact. SO they want the events of Account__e to have seq no. as 1 and Contact__e ones 2, 3 and so on and so forth. To add to the complexity the seq numbers has to be unique, so in our above eg if we do another update on Account in a different transaction the account__e event should start with 4(considering there were only two contact__e events fired earlier) and contact__e events 5, 6 etc and if there are further events fired for custom objects 7, 8 etc

(We tried to do away with sequence number by using the createdDate and also adding a custom datetime field on platform events with millisecond precision but we had different platform events with same created date to millisecond precision.)

I thought of a custom object with a number field and increment it and store in database and fetch and record based on the size of the events sent. And use it something like

public with sharing class Ordering {
public Ordering() {}

public static Integer fetch(){
    List<Ordering__c> order = [Select id, name, sequence_number__c from Ordering__c order by sequence_number__c desc limit 1];
    if(!(order.size() > 0)){
        Ordering__c ord = new Ordering__c(sequence_number__c = 0);
        Insert ord;
        return 0;
    }else {
       return (Integer) order[0].sequence_number__c;
    }   
}

public static void record(Integer seqNumber){
    Ordering__c ord = new Ordering__c(sequence_number__c = seqNumber);
    Insert ord;
}
}

invoke this class in the trigger

Integer sequence = Ordering.fetch();
List<Account__e> accEvents = new List<Account__e>();
for(Integer i = 0; i<trigger.new.size(); i++){
    sequence += 1;
    Account__e acccEvent = new Account__e();
    accEvent.sequence_no__c = sequence;
    //some additional fields
    accEvents.add(acccEvent);
}
EventBus.publish(accEvents);
Ordering.record(sequence); 

Anyone has any other suggestions or improvements to my concept or rather would this concept even work? (Open to constructive criticism as it's absolutely imperative that I get this right as there are others betting on this!)

  • It sounds to me like the feature you really need here is Change Data Capture. It comes with a Transaction Id included on outbound messages, a Replay ID that always increases, and subscribers can obtain change data on multiple objects. – David Reed Jan 29 at 14:06
  • thanks @DavidReed, we did consider CDC at the start but we had other requirement such as a set of generic schema that could be shared with all systems and only generate events based on certain conditions and not just insert and update so we had to go the Platform Events (developer.salesforce.com/blogs/2018/08/…) In regards with replayID PE also has ReplayID but that it not unique across PE objects just within the PE object so it wouldn't work for us! – SFDC_Dev Jan 29 at 14:21
  • 2
    In absence of any other methods as you cannot really guarantee the replay id, your approach may work just fine. You may though like to utilize a custom label/setting here instead of a custom object. However (and that I am not completely aware of your use case here as why would the subscriber really need a sequence number), in my opinion this may be an over engineering for a specific requirement from subscribers. As long as the Contacts will have the related Account's Id, any subscriber should be able to group the records they are receiving at their end. – Jayant Das Jan 29 at 14:26
  • Ah yes, forgot about custom settings altogether, thanks for bringing that up @JayantDas I'll use that instead of custom object! To answer your question about over engineering, there may be instances where the event gateway may be down at their end and when it's up they would need to sort the events and then do the needful b'cos there are could be events received of the related object record first and the parent object later which would throw error at their end. – SFDC_Dev Jan 29 at 14:42
1

It might sound crazy, but hear me out. Handing Sequencing manually would be hellish in my humble opinion and add a couple of concurrent transaction, things turn pretty bad.

You want incremented sequencing right, why not use just 1 platform event Object.

Eg: Changed_Data_Event__e (Object) having 2 fields "Object_Type__c"(Picklist) and "Request_Data__C"(Long Text Area)

Now as you are firing a single type of Event for each Object, for sequencing you can trust replayId. Its autoincremented and always in sequence(YAY). Did i mention its unique too.

List<Changed_Data_Event__e> accEvents = new List<Changed_Data_Event__e>();
for(Account acc : Trigger.new ){
    Changed_Data_Event__e  cdE = new Changed_Data_Event__e ();
    cdE.Object_Type = 'Account';
    cdE.Request_Data__C = JSON.serialize(your fields in wrapper); //Your data in JSON
    //some additional fields
    accEvents.add(acccEvent);
}
EventBus.publish(accEvents);

The only overhead is, the Request_Data__C will be escaped JSON, you have to parse it a bit. But it does what you want. You can also tweak the Request_Data__C creation logic to use custom settings/metadata to add/remove fields in JSON via configurations. Also as its 1 event, you dont have to build multiple listener.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.