A Simple Middleware Framework using the Salesforce Pub/Sub API and Platform Events

grpc from middleware

This post describes a basic approach for implementing middleware that receives Platform Events from Salesforce using the Pub/Sub API.

Motivation

Why use Pub/Sub to communicate with middleware? There a lot of good reasons (asynchronous communication, decoupling, scalability, etc), but in a project I worked on recently the main driver was security. This client was building a middleware layer to integrate Salesforce with several other enterprise systems. For security reasons, the middleware was hosted on an Azure VPN and inbound internet traffic was prohibited. No ports could be opened to allow inbound REST calls from Salesforce. As our result, our initial design, which had Salesforce making REST API calls to the middleware, was not viable.

no inbound to middleware

Instead, we decided to use the Salesforce Pub/Sub API to handle communication between Salesforce and the middleware layer. This API uses gRPC over HTTP/2. The connection is initiated by the middleware opening a long-lived HTTP/2 connection with Salesforce. Because bidirectional streaming is native to HTTP/2, the client and server can send gRPC messages to each other independently over this connection. There is no need for an inbound port on the VPN hosting the middleware.

grpc from middleware

To demonstrate how the Salesforce Pub/Sub API works for this blog post, I created a toy middleware framework along with a Salesforce testing harness that generates Platform Events. You can check out the code in my demo-pubsub repository.

First, let’s look at what happens on the Salesforce side. There is a Platform Event named OrderActivated that gets published whenever an Order is activated and has an effective date. The event captures key information about the Order.

new OrderActivated__e(
  OrderId__c = o.Id,
  Status__c = o.Status,
  EffectiveDate__c = o.EffectiveDate,
  AccountId__c = o.AccountId
)

An OrderActivated event is published by a trigger (OrderTrigger) whenever an Order becomes activated (Status == ‘Activated’). Events are published using the event bus (see: EventBus). The Salesforce event bus is the real-time, central messaging service that enables publish-subscribe. It provides a central queue where publishers send events, and multiple subscribers can receive and process them. This decouples Salesforce from the middleware, allowing it to process Order activations asynchronously.

Our Salesforce testing harness has a simple event publisher that calls the event bus.

public class RealOrderEventPublisher implements IOrderEventPublisher {
  public void publish(List<OrderActivated__e> events) {
    if (events == null || events.isEmpty())
      return;
    EventBus.publish(events);
  }
}

For the subscriber side, I implemented toy middleware (pubsub-middleware.ts) to demonstrate how events are received. I used a Node package (salesforce-pubsub-api-client ) to implement the client that subscribes to the Salesforce Pub/Sub API. For demo pursposes, the toy middleware is run as a child process forked from the test harness. The PubSubApiClient instance is constructed using the scratch org credentials passed into the forked child process environment from the test harness.

const client: any = new (PubSubApiClient as any)({
  authType: 'user-supplied',
  accessToken: process.env.SCRATCH_ACCESS_TOKEN,
  instanceUrl: process.env.SCRATCH_INSTANCE_URL,
  organizationId: process.env.SCRATCH_ORG_ID
});

The client then connects to Salesforce and subscribes to OrderActivated events using the connect and subscribe methods like this.

    await client.connect();
    await client.subscribe('/event/OrderActivated__e', subscribeCallback, 3);
    console.log('⏳ Waiting for events...');

The client.connect() call establishes the Pub/Sub connection to Salesforce. client.subscribe(…) listens on the ‘/event/OrderActivated__e’ channel for events. The function subscribeCallback processes the events. The third parameter, ‘3’, tells the client to keep the connection open until 3 events are received.

In the toy middleware, the event is simply logged to the console. Here is an example of an event received by the client.

{
   "id": "09ef2cc2-ecc4-4ec0-b98f-2cd852ed144d",
   "schemaId": "QaCb2kKCtC-VKCIyVf-O3w",
   "replayId": 27996540,
   "payload": {
     "CreatedDate": 1761827711922,
     "CreatedById": "005Ov00000fx7YvIAI",
     "AccountId__c": "001Ov00001HlY5bIAF",
     "EffectiveDate__c": 1761782400000,
     "OrderId__c": "801Ov00001HlY5eIAF",
     "Status__c": "Activated"
   }
 }

You can see that the four properties from the Platform Event (AccountId__c, EffectiveDate__c, OrderId__c, Status__c) are automatically passed to the client, along with CreatedDate and CreatedById. We also get:

id – the unique id for the platform event
schemaId – the Apache Avro schema use for data serialization (an implementation detail handled by the client implementation in the Node package we have used)
replayId – the id of the last processed event. Used when resubscribing to resume where you left off without processing duplicate events. See Replaying an Event Stream.

Those are the basic components for implementing a solution using the Salesforce Pub/Sub API with Platform Events. On the Salesforce side, you define a Platform Event (OrderActivated) and use a trigger to publish the events to the EventBus (OrderTrigger). On the consumer (middleware) side, you use a Pub/Sub client implementation (salesforce-pubsub-api-client) to connect with Salesforce, subscribe to the channel (/event/OrderActivated__e) and write a callback function to process the events.

For a demo showing how to run the code in the companion repo (demo-pubsub) check out the YouTube video.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top