One Platform Event (I guess two technically) to rule them all

gregheartcloud
4 min readApr 1, 2023

--

The most insane or most genius platform event framework in existence

For my first cross cutting concern with my application utility library I will deal with Platform events. Here are the requirements I have:

— Generic platform event that can both be fired immediately as well as after commit

— Ability to register an interface handler for the actual platform receipt handler

— Ability to turn the platform events on and off

— Platform event data is serialized into a json object that is re-constructed in the handler (if desired)

— Platform event trigger handler framework that is similar in nature to the primary trigger handler stylistically a platform event and trigger can both be built the same way

— Calling ISV application can call the platform event using the namespace in order to drop an event

— Generic async sObject insert and update support. Great for logging or accessing object dml from a different context

— Exceptions to the triggers can be caught or forwarded very easily. This allows me (or customers) to deactivate the triggers upon any form of production incident.

— Platform Events that are not processed are deposited into a dead letter queue (actually this part is not yet implemented but will be in a future feature)

— I will never need to build a platform event ever again… this architecture is 100% scalable across any use case that I have ever come up with. The only thing I would ever need to do is add a new handler implementations (new class) and register the handler (custom metadata)

Ok have a good night and I’ll let you know what I come up with.

… time passes …

This shall hence be known as feature/ENT-14

… time passes …

…more time passes…

Ok its done — at least good enough for MVP. I need a few more features built on top of it but I will get there soon.

I will document the use within the official docs — this blog entry is honestly more for me so that I can remember what in the world I was doing!

My ISV apps only needs to register a platform event handler using a custom metadata handler and implement the very simple interface:

    global interface enterprise.IPlatformEventHandlerClass {
void handle(PlatformEventContext pec);
}

In which the enterprise.PlatformEventContext will provide the following data:

    global class PlatformEventContext{
global String handler{get;set;}
global String eventType {get;set;}
global String eventData {get;set;}
global PlatformEventContext(String eventType, String eventData, String handler){
this.eventType = eventType;
this.eventData = eventData;
this.handler = handler;
}
}

The handlers look like this. I can add them all within one class if I want…. that will make future patch upgrades super simple as I won’t have to add any new metadata (other than perhaps a client side metadata record… stay tuned on that).

global without sharing class PlatformEventHandlers{
global without sharing class InsertSObjects implements PlatformEventDispatch.IPlatformEventHandlerClass {
global void handle(PlatformEventDispatch.PlatformEventContext pec){
List<SObject> sobjs = (List<SObject>) JSON.deserialize(pec.EventData, List<SObject>.class);
insert sobjs;
}
}

global without sharing class UpdateSObjects implements PlatformEventDispatch.IPlatformEventHandlerClass {
global void handle(PlatformEventDispatch.PlatformEventContext pec){
List<SObject> sobjs = (List<SObject>) JSON.deserialize(pec.EventData, List<SObject>.class);
update sobjs;
}
}
}

This corresponds to the following dynamic platform registration handlers:

My own version of Dependency Injection

Here is all it takes to call the framework which currently supports async inserting and updating of sobject lists:

List<Account> acctsToImmediateCreate = new List<Account>();
acctsToImmediateCreate.add(new Account(Name = 'Inserted Immediate1'));
acctsToImmediateCreate.add(new Account(Name = 'Inserted Immediate2'));

List<Account> acctsToAfterCreate = new List<Account>();
acctsToAfterCreate.add(new Account(Name = 'Inserted After3'));
acctsToAfterCreate.add(new Account(Name = 'Inserted After4'));

enterprise.PlatformEventDispatch.insertGenericAfter('enterprise__PE_INSERT_SOBJECT_LIST',JSON.serialize(acctsToAfterCreate));
enterprise.PlatformEventDispatch.insertGenericImmediate('enterprise__PE_INSERT_SOBJECT_LIST',JSON.serialize(acctsToImmediateCreate));

I will update this post later with what the custom client side implementation looks like but time to move on!!! My backlog on this app is not getting smaller.

The cool thing about this is that I have completely built out the entire CI/CD process for the Enterprise Utilities. It is such a simple process once completed:

  1. Create a feature branch for the development work
  2. Approved Pull Request to main which runs all tests and if successful pushes out a new beta release of the managed package. That also tags it to the repository including release notes.
  3. Approved Pull request from main to release branch promotes the latest beta release to a managed release. The app is then automatically installed and all tests are run on the release org.
  4. I can generate a new org locally by running the command:
cci flow run ci_release --org release

Wa la. Full end to end CI/CD for my Enterprise Utilities app thanks to incredible free software from CumulusCI. Thanks to the geniuses at Salesforce.org who provided this software to the world.

Happy coding!

--

--