Event-Driven App Architecture On The Customer 360 Platform

With the release of Platform Events in 2017, we provide to Salesforce Developers a scalable infrastructure for event-based integration. Complimentary to other tools and patterns the Platform supports, this new infrastructure gave way to a loosely-coupled, event-based integration pattern that you have adopted.

But loosely-coupled system integration isn’t the only architecture pattern that Platform Events can address. In fact, you can also pair Platform Events with the platform’s full set of low and pro-code tools to build scalable, event-driven application architectures, too! This alternative approach to app architecture can offer a ton of advantages to developers of all types and skill levels.

That’s what today’s post is all about: using event-driven application architecture to drive better customer experiences and custom apps with the Salesforce Customer 360 platform.

What Is Event-Driven App Architecture?

In traditional applications, interactions between classes (or services) are often based on functions. While doing its work, a given class calling other classes’ functions must understand what those external functions expect as input. That same class must also understand when to invoke those specific external classes, too — and what might happen afterward.

Even if the outside class or service performs a given action (e.g., update this record), the acting service has to understand quite a bit about that outside service to actually use it — and often this approach likewise results in synchronous interactions where the acting service must wait for the outside service to do its thing before continuing on. Generally, this approach is considered “tightly coupled”.

Event-driven architectures are very different in that they are designed to loosen the tight coupling between internal services. It shifts the responsibility of processing onto the responding service rather than the requesting service. This architecture is built around the concept of events.

In practical terms, an event is any action that should drive a change to the overall application state, including the likes of…

  • Data fetch
  • Data change
  • UI action
  • UI update
  • Outbound request
  • etc…

This architecture approach is considered “loosely coupled” because the underlying components of the app are not directly communicating with one another (or, in other words, are not “tightly coupled”). Instead, these components communicate through events. This also allows one-to-many and many-to-one distribution of data between loosely-coupled services.


Event-based architectures are built with three core concepts in mind:

  • Event producer: The “publisher” system, service, or component producing an event for later consumption.
  • Event consumer: The “subscriber” system, service, or component listening for events to consume and act on.
  • Event channels: The distribution and consumption vehicle for events produced.

In the context of an app built on Salesforce, producers and consumers do not need to live outside of the Platform! A Lightning Web Component, a Trigger, a Flow — all of these can produce or consume a Platform event.

With these concepts in mind, different types of application architectures become possible. Every UI interaction or data processing request can be reframed as an event. Producers and consumers of those events can be teased apart from one another. And the resulting services for producing and consuming can then be made simpler, removing unnecessary interaction code.

Why Does Event-Driven App Architecture Matter?

As you’ve likely seen yourself when building apps, the traditional app architecture approach results in tight-coupling between classes or services — a dependency chain to be managed.

In smaller apps, this dependency chain may not be all that difficult to manage. This coupling can become very problematic as your app scales and becomes more substantial. Not only does this approach inhibit horizontal scalability but it typically compounds the maintenance and evolution of each service as the system becomes more complex.

With event-based architectures, interactions between system and services are now being brokered by way of these material events; the event data itself becomes an incredibly useful resource of error-handling and disaster recovery.

In short, there are a number of clear benefits:

  • Improved fault tolerance
  • Improved operational maintenance of services
    • Loosened dependency between services
    • Centralization of data and action processing in context
  • Simplified horizontal scalability
  • Potential performance gains through modular iteration

These benefits are precisely what has driven many software development companies and software developers to adopt and leverage this pattern—including Salesforce ourselves!

Clearly, event-driven architecture has a number of benefits. But how then does the Salesforce Customer 360 Platform support this pattern for your applications? And how can you get started leveraging these platform capabilities to build your own event-driven applications? Let’s take a look at the key features of the Platform that power this kind of approach.

How Do I Get Started?

First things first: let’s review the Platform features that make event-driven application architectures possible.

  • Platform Events
    • Platform Events are the event messages (or notifications) that your apps send and receive to take further action.
  • Change Data Capture
    • Built on top of Platform Events, Change Data Capture can be enabled with a single click and will automatically distribute change events for any changes made to any standard or custom object records.
  • Asynchronous Apex & Callouts
    • Async Apex allows you to write Apex code that can be processed, well, asynchronously : ) The same holds for Callouts to external systems with Async Callouts.
  • Asynchronous Triggers
    • You can also handle triggers asynchronously thanks to Platform Events by creating events from an object trigger and then adding trigger logic to the event itself (rather than the object).

What Are The Patterns I Should Consider?

Using the above platform capabilities, you can now begin to tackle various parts of your application design with event-driven architecture in mind. There are a number of scenarios where eventing can help create more efficient and enjoyable user experiences. Examples for each of these follow.

Event-driven Flows For Record Processing

Eventing can also be useful in Flows, where you can leverage events to kick off flows and have flows themselves connect to one another through events rather than through direct interaction.

By using events to drive Flows, you can achieve some really valuable separation of concerns and stepwise refinement of your Flows. Rather than using Subflows, you can have one Flow create an event record and then have another Flow that kicks off when a Platform Event is received.


This allows Flow-driven automations or low-code development to more easily context-bound each individual flow, reducing the amount of complexity in a single flow and allowing each to be maintained and iterated on independently — providing the inputs and outputs are consistent.

Event-driven Async Call-Outs

For more advanced Salesforce developers, working with Governor Limits is a critical part of building scalable apps successfully. But oftentimes the necessary constraints of the Platform can make certain development approaches problematic. Working extensively with nested and chained callouts to external web services, for example, often causes issues.

For example, callouts (web requests) from a DML context is not possible. In the DML context, records get locked, and the Platform doesn’t allow locking records for an undetermined amount of time while waiting for a response from a callout. That’s why we have tools like @future. But even these tools limit techniques like chaining. A future context can’t again call out in the future, for example.

Events can provide an alternative method for asynchronous processing, as each future callout can instead be tied to an Event and processed independently rather than as a bundled set of asks during a single request.

Event-driven Triggers For Record Processing

By leveraging events for record processing and trigger logic, you can simplify the management and maintenance of triggers across objects by centralizing the processing code, and where it makes sense, processing trigger logic post-commit.

Let’s dig into a practical example of this particular approach. There are a ton of benefits to moving from traditional record processing and triggers to event-based ones.

Practical Example: An Async Trigger

Event-based triggers are hinged off of event objects instead of the standard or custom object itself. This allows you to perform resource-intensive business logic asynchronously in the change event trigger and keep transaction-based logic in the Apex object trigger. By decoupling the processing of changes, change event triggers can thus help reduce transaction processing time.

So what’s involved in adapting to the new approach? Let’s compare a traditional trigger to an event-driven one in a scenario where we’re taking action when an Account object record is updated.

Traditional Trigger

In a traditional trigger, our logic is tightly coupled with the record insert or update process for a given record:

trigger AccountTrigger on Account (after insert, after update, before delete) {
    if(Trigger.isAfter) {
    
        if(Trigger.isInsert) {
            AccountTriggerHandler.afterInsert(Trigger.newMap);
            
            /* Determine What The Update Context Is */
            /* Isolate Specific Context */
            /* Do Specific Record Processing Task */
        }
        
        if(Trigger.isUpdate) {
            AccountTriggerHandler.afterUpdate(Trigger.newMap);
            
            /* Determine What The Update Context Is */
            /* Isolate Specific Context */
            /* Do Specific Record Processing Task */
        }
        
        /* ... */ 
    }
}

In this case, we have a single chokepoint for all of the application logic relating to any and every record update that might affect Accounts. Even in the best-case scenario, we’ll have many different code references to external, dependent classes in order to deal with the specific update logic given the entire org’s Trigger automation around Account object updates.

As experienced Salesforce Devs can attest to, this can lead to some real headaches in maintaining and scaling Trigger code as your org grows.

Event-Driven Trigger

With event-driven triggers, we hinge the trigger to a Platform Event rather than to the core or custom Object Trigger itself. This allows us to more loosely couple the trigger action from the object-specific triggers.

In this case, we isolate the application logic for the very specific record processing purpose (say, generating a PDF from the update) to the trigger on a Platform Event we’ve defined specifically for this purpose (e.g., Account_Update_For_Purpose_A__e).

trigger Async_CallTrigger on Account_Update_For_Purpose_A__e (after insert) {
    
    Map<Id,String> accountsUpdated = new Map<Id,String>();
    Set<Id> accIds = new Set<Id>();
    for(Account_Update_For_Purpose_A__e ac:Trigger.new) {
        accIds.add(ac.Record_Id__c);
    }
    Map<Id,Account> accMap = AccountTriggerHandler.getAccounts(accIds);
    
    /* Do Specific Record Processing For Purpose A */
    
}

This vastly simplifies the application logic and improves both readability and maintainability of the code for this purpose. It also allows us to test and improve this code without creating unnecessary surface area for inadvertent bugs or performance issues affecting another record processing task.

What Should I Watch Out For?

Of course, from one group of developers to another, it behooves us to make it clear that, as with every application architecture approach, there are pros and cons to consider. Event-driven app architecture is not a magic bullet.

When applying this pattern, there are a few constraints to be aware of:

  • While bulkification limits in the context of an event trigger are 10x higher than traditional triggers, other governor limits are not likewise multiplied 10x.
  • Platform Events, and delivery, are still single-threaded and could fall beyond the “transaction” processing context, in terms of elapsed time, if there are many other events in the bus.
  • Platform Events are designed to be eventually consistent which means there are rare circumstances that an event may not propagate when expected and, rarer still, at all.

Of course, it likely goes without saying that, as with all of our Platform infrastructure, we continue to invest materially in increasing limits and improving performance throughout. In the not-too-distant future, the numbers published here and in the Platform Events Limits documentation will need to be updated to reflect the current reality, and that’s a great thing.

Where Do I Learn More?

Event-based architectures on the Salesforce Platform are becoming more and more common, and as you can probably tell from what we’re doing with Lightning Web Components recently and IoT in the past, these architectural patterns are core to the present and future of Salesforce development and the Salesforce Customer 360 Platform.

If you want to learn more about going hands on with Platform Events, and you’re not sure where to start, we’ve pulled together some great content on Trailhead to get Salesforce Trailblazers of all skills exposed to the approach:

You can also learn more about the patterns discussed earlier in the following Salesforce Developer Documentation:

How Can I Get Engaged?

As always, we love hearing from our vibrant Developer Community, so if you have great real-world examples to share, or your own thoughts on the pattern, we welcome it! We’d love to highlight some additional architectures in the future—maybe even with a public webinar for a broader show-and-tell of your solutions, strategies, and successes.

To help you get started, we’ve cooked up reusable, generic event-driven trigger framework that you can use to explore this approach for yourselves. You can find it here: https://github.com/SE-Assets/Async-Trigger-Framework

Dig in, and don’t hesitate to fork or even submit a pull request if you think it can be improved further!

About The Authors

This article was put together in collaboration by folks in the Technical Architecture team at Salesforce who love digging into app architecture patterns and working with customers to push what’s possible with the Salesforce Customer 360 Platform.

Jeffrey Garwood is a leader of Salesforce Pre-Sales Technical Architects with a long-standing background in enterprise software and architecture. Pete O’Connell and Frank Caron are peer Technical Architects supporting Salesforce customers from New York City.

This team was readily and thankfully supported by a broader team of Salesforce Trailblazers, including Jay Hurst, John Brock, Gaytri Khandelwal, Arastun Efendiyev, and the Developer Evangelist and Content teams.