Using Event Platform to connect Salesforce and other services

Sometimes is hard, without using expensive pretty tools like MuleSoft, to build a robust Event-driven architecture with Salesforce. In this article we will guide you through setting up everything to start dispatching events related to your Salesforce processes.

Event driven architectures

First things first. If you ever listened about this kind of architecture, there are plenty of articles describing it, but in short, an event driven architecture uses events to send messages or data between decoupled services. A message could be for example, in an e-commerce website, that a new order is placed. This message could be listened by multiple services, for example:

  • Stock: We must decrease the available quantities of each item of our order, so our website will display if any items are now out of stock.
  • Invoicing: We should prepare an invoice for this order and add it to our records.
  • Picking: We should send an alert to our warehouse to locate all the items and prepare them for shipping.
  • Shipping: Also, sure our package delivery company has an API, so we should call their webservices to get a shipping label to add to our order and ask for its pickup.

As you can see, we can decouple each of our services, so they can live in different instances, or they can be managed by different teams, or companies. Also, these services can listen only for some events, for example, it is probable that if we have an event that notifies that a product has been restock, only stock service will be listening to it, as invoicing, picking, or shipping will not be doing anything with this piece of information.

Salesforce Platform Event

Salesforce has its own implementation of this principle with its Platform Events. In this case, you can publish messages to the Event Bus in multiple ways, like Process Builder, Flows, using the REST/SOAP API, or by coding in APEX.

It is important to read their considerations when implementing platform events, as you can hit sometimes the governor limits. Also, starting on the Spring ’21 release (API version 51.0) Salesforce added some improvements and now you are able to configure the user and batch sizes for the Platform Event triggers.

Now sure you want to see some code…

Creating Our Event

Salesforce Events are almost standard SObjects, the main difference is how we define them, and their name that ends with a ‘__e’, instead of custom objects that end with ‘__c’.

There are multiple ways to define them, in our case we will be defining them using the Metadata API, so open Visual Studio Code and create the objects directory. Following our previous example, we will create a new file called Order_Created__e.object-meta.xml.

<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="<http://soap.sforce.com/2006/04/metadata>">
    <deploymentStatus>Deployed</deploymentStatus>
    <description>Order creation event</description>
    <eventType>HighVolume</eventType>
    <label>Orders Created</label>
    <pluralLabel>Orders Created</pluralLabel>
    <publishBehavior>PublishAfterCommit</publishBehavior>
    <fields>
        <fullName>Contact_Id__c</fullName>
        <externalId>false</externalId>
        <isFilteringDisabled>false</isFilteringDisabled>
        <isNameField>false</isNameField>
        <isSortingDisabled>false</isSortingDisabled>
        <label>Contact ID</label>
        <length>18</length>
        <required>true</required>
        <type>Text</type>
        <unique>false</unique>
    </fields>
    <fields>
         <fullName>Order_Id__c</fullName>
        <externalId>false</externalId>
        <isFilteringDisabled>false</isFilteringDisabled>
        <isNameField>false</isNameField>
        <isSortingDisabled>false</isSortingDisabled>
        <label>Order ID</label>
        <length>18</length>
        <required>true</required>
        <type>Text</type>
        <unique>false</unique>
    </fields>
</CustomObject>

This event will have two properties, the ID of our customer, and the ID of the order. You can define as many custom fields as you need (the same amount you can add to an SObject) of Checkbox, Date, Date/Time, Number, Text, or Text Area (Long) types.

Depending on lots of things, you should choose between creating lots of custom fields with all the information required in the message, of just the information in order the subscriber is able to get all the information they need (Like the previous one). The article “Killing the Command message: should we use Events or Documents?” from Microsoft, even it is a little old, it is very self-explanatory.

Dispatching the event

Following the example and in our case, Salesforce receives the order details only when it has been successfully paid, so when a new Order__c record is created, we should dispatch this event.

To achieve this, we could use the Process Builder or Flows, but as it is easy to configure using the Setup page, we will be using APEX in this article, so you can fit this to your needs.

We will use standard SObject triggers on the Order__c custom object to dispatch a new Order_Created__e:

trigger OrderCreatedTrigger on Order__c (after insert) {
    for (Order__c order : Trigger.new) {
        Order_Created__e event = new Order_Created__e(
            Contact_Id__c = order.Contact__c,
            Order_Id__c = order.Id
        );

        Database.SaveResult result = EventBus.publish(event);
        if (!result.isSuccess()) {
            // Notify errors
        }
    }
}

To notify Salesforce about the event, we use the EventBus class.

Listening to the event (Using APEX)

Now, we must listen to that published event. As mentioned before, we can use straightforward ways to listen to it like Flows/Process Builder, but here we will show the hard way, we will demonstrate here how we can make a call to a webservice from our shipping company, to create a tracking number for it.

trigger GetTrackingNumberFromShippingCompany on Order_Created__e (after insert) {    
    for (Order_Created__e event : Trigger.New) {
        System.enqueueJob(new ShippingCompanyBuildLabelJob(event.Order_Id__c));
    }
}

Remember you should not make long tasks in triggers (Even they are platform events), so the best way to make asynchronous calls to external webservices, is to use Queueable classes. Even this is not the best way, as the details about shipping providers and callouts should be wrapped in other service (Ex: ShippingService), we preferred to add them in this example:

public class ShippingCompanyBuildLabelJob implements Queueable {
    static final String CALLOUT = 'callout:ups';
    public String orderId;

    public ShippingCompanyBuildLabelJob(String id) {
        ShippingCompanyBuildLabelJob.orderId = orderId ;
    }
    
    public void execute(QueueableContext qc) {
        Order__c order = OrderRepository.findOneByIdForShipping(order)

        ShippingDetails shipping = new ShippingDetails();
        shipping.Name = order.Contact__c.Name;
        shipping.Address = order.Contact__r.Address__c;
        shipping.Zipcode = order.Contact__r.Zipcode__c;
        shipping.Email = order.Contact__r.Email;
        shipping.Phone = order.Contact__r.Phone;
        shipping.Reference = order.Reference__c;

	try {
        String trackingNumber = ShippingCompanyBuildLabelJob.getTrackingNumberFromProviderOrFail(shipping);
        OrderRepository.updateTrackingNumber(order.Id, trackingNumber);
	} catch (Exception ex) {
	    // Notify about errors
        }
    }

    private String getTrackingNumberFromProviderOrFail(ShippingDetails shipping) {
        Http h = new Http();
        HttpRequest httpReq = new HttpRequest();

        httpReq.setMethod('POST');
        httpReq.setHeader('Content-Type', 'application/json'); 
        httpReq.setEndpoint(CALLOUT);
        httpReq.setBody(JSON.serialize(shipping));

        HttpResponse res = h.send(httpReq);
        if (res.getStatusCode() != 200) {
            throw new InvalidResponseException();
        }
        Map<String, String> m = (Map<String, String>)JSON.deserializeUntyped(res.getBody());

        return values.get('trackingNumber');
    }

    class UPSShipping {
        public String Name;
        public String Address;
        public String Zipcode;
        public String Email;
        public String Phone;
        public String Reference;
    }
}

Listening to the event (Using AWS AppFlow / EventBridge)

Other way to listen to platform events can be Amazon Web Services, using the AppFlow service. You can deliver these messages to multiple destinations like Lambda, S3, Kinesis…

So, login in your AWS console, click on Services and look for Amazon AppFlow, where on the first step, we should add a name for our flow, and the encryption details. After clicking in Next is where we must start adding details, so choose Salesforce from the Source dropdown, and create a new connection.

On the destination, we will choose Amazon S3, but using AWS EventBridge we will be able to connect to other multiple AWS services:

Now, on the third step, we can choose the fields we are going to sync, and make some transformations if required:

On the last step, check everything is correct, and click on Create Flow.

Now, each event will create a new object in the S3 bucket, but using this source connection with EventBridge, you can make calls to multiple destinations:

We hope you find this article helpful! Remember you can ask anything in the comments section =)

One comment

Leave a Reply

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