Platform Events — My New Favorite Tool
According to my Trailhead history, I completed the Platform Event Basics module on 12/9/17. So it's more than a little while since I first learned about them. But just in the last month or so I've kinda become obsessed.
When I first read that module I barely understood it. And it's no wonder! The intro to the first section suggests understanding object oriented programming (I barely do.) and that "familiarity with the concepts of Streaming API(CometD) and Pub/Sub API is helpful for this module although not required." I have zero familiarity with those concepts. The hands-on challenge requires Apex triggers. I'm no coder. I know enough to be dangerous or to complete a Trailhead challenge, but I would never write code to go into a client org!
All that is to say that the concept of platform events might seem a little intimidating to you. That's OK. It was intimidating to me as well. But I have realized that they can actually be pretty simple and incredibly useful. That's why I want to share with you!
The Concepts Needed to Ferment
Like I said, it's almost five years since I first learned about platform events from a Trailhead badge. At almost no time in the interim did I consider that I might have a use case for them. They sounded very developer-y. But the knowledge was tucked away, apparently.
In January 2021 I completed Application Integration Patterns for Salesforce Lightning Platform. (There's an exciting sounding badge, right? I think I must have been bored. Or working toward some arbitrary number of new badges.) That was yet another badge that didn't seem to have a lot of relevance for me. And it was almost two years ago. But apparently concepts from that badge started rattling around my brain, eventually sticking themselves to platform events, which were also bouncing around in there.
Pub/Sub (short for Publish/Subscribe)
If you don't want to read that whole application integration patterns module (And why would you?), I think of pub/sub as breaking apart the act of writing something from the need for anyone to read it. Like writing a blog, sending out a tweet, or even posting on Facebook, the "publish" side of the action is something done only by the writer. Or maybe it's more like a town crier, shouting their message that "It's ten o'clock and all's well!" Or the muezzin chanting the dawn call to prayer from the top of the minaret, "It is better to pray than to sleep."
Somewhere, the writer or the crier hopes, there is someone "subscribing" to their message. I sincerely hope, when I write one of these posts, that you subscribe and read. The muezzin sings not for his own benefit but to draw people to the mosque. Theoretically we could all just be shouting into the void.
I guess one difference from my metaphor is that a pub/sub integration pattern has, by definition, some means of subscribing. That's what makes it an integration pattern.
Fire and Forget
This is the one that really didn't make a lot of sense to me at first. The "forget" part kinda' freaks me out! Do I want to work with any kind of integration that just sends off a message without knowing that it was received? It's like sending an SMS without even the notification that it was "delivered." Even when you get that—or even better, a "read" receipt—do you really believe the text was read before you hear back from your friend?
I suppose any uni-directional integration is kinda' fire-and-forget, since the sending system doesn't necessarily get back data from the receiver. But I've mostly worked with middleware such as Zapier. The sending system might not get data back, but Zapier at least knows it has successfully taken action on the receiving side. So Zapier could report an error if needed.
I'm not certain I can explain the difference between the two concepts, now that I think about it. "Pub" and "fire" seem basically the same to me. "Sub" can't be a whole lot different than making sure your "fire" is aimed somewhere that's ready to receive, can it? Maybe someone smarter about these things can help me out?
Are Subscriptions Reliable? Can you fail to notice a platform event?
Despite my lack of understanding, those combined concepts must have fermented in my subconscious. I think I had a vague notion that something called a "platform event" could be "published" (or "fired") and that a "platform event fired flow" could "subscribe" to listen for it. But I didn't know if I was missing some important context or nuance.
And I really didn't know if it was reliable. Could the subscriber ever miss a delivery? I subscribe to the daily hard-copy newspaper, but there are plenty of days when delivery is late, never arrives, or the paper is wet and unreadable. Even e-newsletter subscriptions sometimes get diverted into my spam folder. And just because I "subscribe," that says nothing about whether I read the paper, let alone any given section or article.
These were the questions that I've had about platform events for several years now. And platform events don't seem to get mentioned very often in blogs or presentations, so I didn't really learn anything more.
Then at the end of August I stayed in the Amherst, MA, home of Salesforce MVP and brilliant system architect Chris Pifer, who I've worked with many times at Open Source Sprints. We were nerding out late into the night over Salesforce stuff. (As one does.) At some point Chris mentioned that he had been using platform events recently to allow for an interconnected and extensible set of automations in a large enterprise implementation. (At least, I think that's what he described. Honestly, he was way over my head.)
That lead me to ask Chris some of my questions about platform events, including the big one that worried me: Could the subscriber miss the publication? Chris assured me it could not. I don't remember if this is how he explained it or not, but what I eventually took away was that the pub/sub of a platform event is similarly reliable to the create/update of a record and the "noticing" of that event by a record triggered flow.
My gears started turning...
Potential Use Cases Come Calling
Mass Invoice Insert
Over the summer I had a client with a need to create a whole bunch of invoice records each month. I created a screen flow that has a pause before calling a subflow, so the large amount of records that need inserting happens as part of a separate transaction. (In fact, there's another record-triggered flow that creates child records of those records, also after a pause.) I was concerned that even with the pauses my flow might still fail. What if the screen flow designated more records to be created than the subflow could handle without hitting limits? I tested as best I could in a sandbox and then crossed my fingers when I got to production. It works, but I still have a nagging suspicion that it could be brittle. (It's on my list to refactor, but for now we go with, "It ain't broke. Don't fix it.")
Frequent Recalculation of Child Records
Then in September I started work on a tangle of flows for another client,
The Modern Classrooms Project. That task turned up an interesting dilemma: One of the flows we needed would fire upon delete of an opportunity product, calling a related flow that would determine needed revenue recognition child records for the opportunity. (Let's abbreviate "revenue recognition" to just "revrec.") But flows can only fire in a Before Delete context. That meant that our flow picked up the deletion of the opportunity product and called the opportunity flow to recalculate revrec records. But that recalculation ran before the product was deleted, which is functionally the same as not running at all in this case. I reached out to the community looking for suggestions to force my flow into an After Delete context. In the course of that discussion I wondered, after my conversation with Chris, whether we could have the before delete flow publish a platform event, then fire a platform event fired flow that would be in an after delete context? Consensus was that it might, but the only way to be sure would be testing. (The easiest option the community suggested was to pass the Id of the deleted record to the revenue recognition flow to be excluded in the Get Records for products. I went with the easier—and guaranteed—option.) But there was enough suggestion that it might work to make me want to revisit the question later.
The Moment Arrives
That same Modern Classrooms tangle of flows threw us another curveball that finally gave me the excuse to try out platform events. We realized that we need to force recalculation of revrecs for opportunities if they cover the period in which a new session is going to happen, or if a session's dates change to move it into or out of the period of a contract. Modern Classrooms is growing fast. I could envision a time not too long from now when there could be dozens or even hundreds of opportunities impacted when a session changes. I thought I could probably use the paused flow trick to process those changes asynchronously. But the idea of trying out platform events to process those changes closer to realtime seemed worth a bit of playtime.
And it worked! It was actually surprisingly easy.
Platform events are like Salesforce custom objects, in that they can have fields with data in them. But platform events do not persist in the database as records. (And they can't be viewed in the user interface at all.) Platform events are metatdata and get configured, similar to a custom metadata type (CMDT). But unlike a CMDT, you can fire a flow when a new platform event is published. (I wanted to write "call a flow from a platform event record," but I think saying "record" would be confusing because that platform event "record" only exists long enough to be heard by a subscriber, there is no "record" sitting in the database for later viewing.)
To expand my metaphors from earlier, a platform event isn't just published, it isn't just shouted into the sky—it can carry a payload of meaning. The town crier doesn't just make noise at 10pm, he can deliver a message that "All's well!" or "A storm's a-comin'!" Maybe it's the difference between a public clock that can chime only the hour and a PA system that can make a sound on the hour or give a news bulletin. Or it's the difference between birdsong that is "just sound" to us, but has meaning for the other members of the species. Or the hideous noise of a fax machine or modem that actually sends information from one device to another. Or the random-looking pattern of squares on a QR code that your camera can interpret as a web address. Or, you know, squiggles on a screen that you and I interpret as "writing."
[Enough metaphors for you yet? I could probably come up with more.]
So the heart of my first platform event-based solution was simple: I made a platform event that would carry a very small payload: the Id of a single opportunity, one whose revrecs need recalculation. When a session is created or updated, I have a flow that finds all the opportunities that are impacted and defines platform events for all of them. Then it publishes all those platform events, one for each opportunity. One platform event is shouted into the world for each opportunity that needs revrecs recalculated.
Meanwhile a platform event triggered flow is subscribed to The Opportunity Network. Each opportunity Id, as it's shouted, triggers a very simple flow that finds the opportunity and checks a box on it called "Recalculate RevRecs." Can't get a lot simpler than that, right?
There's one more flow in our solution. It's a record triggered flow on Opportunity. If Recalculate RevRecs is checked, it calls the revenue recognition flow and then it unchecks the box (to ready it for use in the future.)
Depending on your Flow skills, this might either sound very simple or a little complicated. But I think you can follow that each individual flow is pretty understandable. So stick with me.
One to Many to One
The cool part is that with platform events we've now taken a single user action (create or edit of a session) and broken its consequence (need to update dozens or hundreds of opportunities) into individual pieces.
In normal flow land—whether we do this in one big flow or break the work into a triggering flow and one or more subflows—a single transaction (the create or edit of one session) is going to result in a cascade of record updates (changes to the revrecs for many opportunities). Depending on the complexity of that cascade and how many records it happens for, that could be a whole lot of processing going on. And in normal flow land, all of that processing will happen within a single "transaction" that initiated the flow, so all of the processing together has to stay within the Salesforce governor limits.
But instead of normal flow land, we process each opportunity individually because they're each kicked off by an individual platform event. This isn't a collection of "opportunities that need reprocessing" anymore. No worries about CPU timeouts, or flow element limits, or a slow page reload after Save. The session is created or updated and a very efficient flow in the background publishes some platform events. Easy peasy. Then one opportunity, then another, then another, in series or in parallel, it doesn't really matter, all do their revrec recalculation. The user experience is fast because the session save and publishing of events is all that happens in the user's transaction. Everything else is in the background.
Get Around Specific Limits
WIthin a week of finding using platform events for that cool case, we found another way to use them at Modern Classrooms. This time we're going to evade a very specific limit: You can't send more than 10 emails in a single transaction. Obviously you can send email templates and the like but most of the time you're sending a single one in a transaction. ("When an opportunity is moved to Closed Won, email the donor an acknowledgement." That's one email.) But if you want to call the sendemail() method (or, in a flow, use the Send Email action) you can't do it more than ten times in one transaction. This one's tripped me up before!
But this is exactly what my colleague Terri wanted to do. She was building a screen flow to allow you to select multiple records in a list view and then send out a list email to the contacts that go with those records, similar to the native List Email function on campaigns. I mean, why should campaign members have all the fun?
Terri's a fast learner and not afraid to try things out. So she built a little screen flow and dropped it onto a list view and tested it a couple of times in a sandbox. But when she mentioned it in a meeting, I asked if she had attempted it with 15 or 20 records. She had not. (There are never enough records in a sandbox!)
I was pretty confident it was going to hit that send limit. Worse yet, the Send Email action has a limit of five email recipients among the three places you can add them.
But I immediately wondered if platform events—my new favorite toy!—could come to the rescue. Turns out that they can!
All we had to do was define a platform event with more payload:
Set up the screen flow to publish a bunch of platform events, one per selection on the list view.
Then a waiting platform event triggered flow (below) takes those fields and puts them into the right places on a Send Email action. Since that platform event triggered flow is working on just a single email, there's no problem with limits. In fact, that flow can even take the step to look up the contact who's the recipient (we originally sent from related records) and could look up records related to that contact...
The freedom of working from an entirely separate transaction opens up lots of possibilities.