Extend Flows with Heroku Compute: An Event-Driven Pattern

This post previously appeared on the Salesforce Architects blog. Event-driven application architectures have proven to be effective for implementing enterprise solutions using loosely coupled services that interact by exchanging asynchronous events. Salesforce enables event-driven architectures (EDAs) with Platform Events and Change Data Capture (CDC) events as well as triggers and Apex callouts, which makes the […]

This post previously appeared on the Salesforce Architects blog.

Event-driven application architectures have proven to be effective for implementing enterprise solutions using loosely coupled services that interact by exchanging asynchronous events. Salesforce enables event-driven architectures (EDAs) with Platform Events and Change Data Capture (CDC) events as well as triggers and Apex callouts, which makes the Salesforce Platform a great way to build all of your digital customer experiences. This post is the first in a series that covers various EDA patterns, considerations for using them, and examples deployed on the Salesforce Platform.

Expanding the event-driven architecture of the Salesforce Platform

Back in April, Frank Caron wrote a blog post describing the power of EDAs. In it, he covered the event-driven approach and the benefits of loosely coupled service interactions. He focused mainly on use cases where events triggered actions across platform services as well as how incorporating third-party external services can greatly expand the power of applications developed using declarative low-code tools like Salesforce Flow.

As powerful as flows can be for accessing third-party services, even greater power comes when your own custom applications, running your own business logic on the Salesforce Platform, are part of flows.

API-first, event-driven design is the kind of development that frequently requires collaboration across different members of you team. Low-code builders with domain expertise who are familiar with the business requirements can build the flows. Programmers are typically necessary to develop the back-end services that implement the business logic. An enterprise architect may get involved as well to design the service APIs.

However you are organized, you will need to expose your services with APIs and enable them to produce and consume events. The Salesforce Platform enables this with the Salesforce Event Bus, Salesforce Functions, and Streaming API as well as support for OpenAPI specification for external services.

Heroku capabilities on the Salesforce Platform include event streaming, relational data stores, and key-value caches seamlessly integrated with elastic compute. These capabilities, combined with deployment automation and hands-off operational excellence, lets your developers focus entirely on delivering your unique business requirements. Seamless integration with the rest of Salesforce makes your apps deployed on Heroku the foundation for complete, compelling, economical, secure, and successful solutions.

This post focuses on expanding flows with Heroku compute. Specifically, how to expose Heroku apps as external services and securely access them via flows using Flow Builder as the low-code development environment. Subsequent posts will expand this idea to include event-driven interactions between Heroku apps and the rest of the Salesforce Platform as well as other examples of how Salesforce Platform based EDAs address common challenges we see across many of our customers including:

  • Multi-organization visibility and reporting
  • Shared event bus designs
  • B2C apps with Lightning Web Components

Building Salesforce flows with your own business logic

Salesforce external services are a great way to access third-party services from a flow. All you need are the services’ OpenAPI spec schema (OAS schema), and you’re set to go. There are some great examples of how to register your external services here, with a more detailed example of how to generate an Apex client and explore your schema here.

But what if you want to incorporate custom business logic into your flow app? What if you wanted to extend and complement the declarative programming model of flows with an imperative model with full programming semantics? What if you wanted to make your app available to flow developers in other organizations, or possibly accessed as a stand-alone service behind a Lightning Web Components based app?

This kind of service deployment typically requires off-platform development, bringing with it all the complexity and operational overhead that goes with meeting the scalability, availability, and reliability requirements of your business critical apps.

The following steps show you how you can deploy your own apps using Heroku on the Salesforce Platform without any of this operational overhead. We’re going to walk through an example of how to build and deploy custom business logic into your own service and access it in a flow. Deployment will be via a Heroku app, which brings the power and flexibility to write your own code, without having to worry about the operational burden of production app deployment or DevOps toolchains.

This approach works well in scenarios where you have programmers and low-code builders working together to deploy a new app. The team first collaborates on what the app needs to do and defines the API that a flow can access. Once the API is designed, this specification then becomes the contract between the two teams. As progress is made on each side, they iterate, perfect their design, and ultimately deliver the app. All the code used for this example is available on GitHub, so that you can try it out for yourself.

Note: Apex is a great way to customize Salesforce, but there are times when a standalone app might be the better way to go. If your team prefers Python, Node, or some other programming language, or perhaps you already have an app running on premises or in the cloud, and you want to run it all within the secure perimeter of the Salesforce Platform, a standalone Heroku app is the way to go.

API spec defines the interface

The example application an on-line shopping site that lets users login, browse products, and make a purchase. We’ll describe the process of building out this app in a number of posts, but for this first part we’ll simply build a flow and an external service that lists products and updates inventory in Salesforce. For the API, we’re using a sample API available on Swagger Hub. There are a variety of tools and systems that can do this, including the MuleSoft Anypoint Platform API Designer. For this example, however, we’re using this simple shopping cart spec to bootstrap the API design and provide the initial application stub for development.

From the API spec, API Portals can produce server side application stubs to jumpstart application development. In this example, we’ve downloaded the node.js API stub as the starting point for API and app development. We’ve also modified the code so that it can run on Heroku by adding a Procfile and changing the port configuration. If you want to try it yourself, you can Deploy to Heroku, or click the Deploy to Heroku button on the GitHub page.

Let’s begin by looking at the initial API spec for the application. These API docs are being served from a deployment of the app stub on Heroku. The actual YAML spec (which we will modify later in this post) is included in the repo as well.

As you can see in the spec, there are definitions for each of the methods that specify which parameters are required and what the response payload will look like. Since this is a valid OpenAPI spec, we can register this API as an external service as described in Get Started with External Services.

External service authorization

The flow needs a Named Credential in Salesforce to access the external service. Salesforce offers many alternatives for how the app can use the Named Credential including per-user credentials that can help you track and control access. For this example, though, we’re going to use a single login for all flow access using basic HTTP authentication.

You can find the code that implements this method is in the app included in the repo.

App access to the organization is authorized via a Salesforce JWT account token and implemented in the app in SFAuthService.js:

'use strict';

const jwt = require('salesforce-jwt-bearer-token-flow');
const jsforce = require('jsforce');

require('dotenv').config();

const { SF_CONSUMER_KEY, SF_USERNAME, SF_LOGIN_URL } = process.env;

let SF_PRIVATE_KEY = process.env.SF_PRIVATE_KEY;
if (!SF_PRIVATE_KEY) {
    SF_PRIVATE_KEY = require('fs').readFileSync('private.pem').toString('utf8');
}

exports.getSalesforceConnection = function () {
    return new Promise(function (resolve, reject) {
        jwt.getToken(
            {
                iss: SF_CONSUMER_KEY,
                sub: SF_USERNAME,
                aud: SF_LOGIN_URL,
                privateKey: SF_PRIVATE_KEY
            },
            (err, tokenResponse) => {
                if (tokenResponse) {
                    let conn = new jsforce.Connection({
                        instanceUrl: tokenResponse.instance_url,
                        accessToken: tokenResponse.access_token
                    });
                    resolve(conn);
                } else {
                    reject('Authentication to Salesforce failed');
                }
            }
        );
    });
};

The private key is configured in Heroku as a configuration variable and is installed when the app is deployed.

Register the external service methods

Individual methods for the ShoppingService external service are easily added to a flow just as they would be for any external service. Here we’ve added the Get Products and Get Order methods, as shown in Flow Builder below. But since Flow Builder can register an external service method using only the API spec, they are just references to the stub methods that we still need to build out. We’ll program something for them later in this post.

These are familiar steps to anyone that has registered an external service for a low, but if you want more detail on how to do this, check out the Get Started with External Services Trailhead.

Define the API and build out the methods

With the authorizations in place and the methods defined, we are now ready to build out the external service in a way that meets our company’s specific needs. For this, we need to implement each of the API methods.

To illustrate this, here is the Node function that has been stubbed out by the API for the Get Order method. It is here that your business logic is implemented.

For each of the API methods, we’ve implemented some simple logic that we will use to test interactions with the flow. For example, here’s the code for getting a list of all orders:

/**
 * Get list of all orders for the user
 *
 * type String json or xml
 * pOSTDATA List Creates a new employee in DB (optional)
 * returns List
 **/
exports.typePost_orderPOST = function(type,pOSTDATA) {
  return new Promise(function(resolve, reject) {
    var examples = {};
    examples['application/json'] = [ {
  "Item Total Price" : 1998.0,
  "Order Item ID" : 643,
  "Order ID" : 298,
  "Total Order Price" : 3996.0
}, {
  "Item Total Price" : 1998.0,
  "Order Item ID" : 643,
  "Order ID" : 298,
  "Total Order Price" : 3996.0
} ];
    if (Object.keys(examples).length > 0) {
      resolve(examples[Object.keys(examples)[0]]);
    } else {
      resolve();
    }
  });
}

You can examine the code that implements each of the methods in the repo:

  • Get Products Method
  • Post Order Method
  • Get Order Method
  • Get Orders Method

Now that we have some simple logic executing in each of these methods, we can build a simple flow that logs in using the Named Credential, accesses the external service, and returns product data.

Running this flow shows the product data from the stub app. The successful display of product data here indicates that the flow has been able to successfully log in to the app, call the Get Product method, and get the proper response.

Update the API

So now that we have our basic flow defined and accessing the app, we can complete the API with new methods necessary for the app to do what it needs to do.

Let’s imagine that the app has up-to-date product inventory data and we want to use that data to update the Product object in Salesforce with the current quantity in stock. For this, the app would need to be able to access Salesforce and update the Product object.

To do this, the flow needs to make a request to a Get Inventory method. But that method does not yet exist. However, we can modify the API to include any new methods we need. Here our teams work together to determine what the flow needs and what methods are necessary in the app.

After discussion, we determine that a single Get Inventory method will satisfy the requirements. So, now we update the API spec to include a new method:

/{type}/get_inventory/:
    get:
      tags:
      - "Internal calls"
      description: "Get Inventory"
      operationId: "typeGet_inventoryGET"
      consumes:
      - "application/json"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "type"
        in: "path"
        description: "json or xml"
        required: true
        type: "string"
      - name: "product_id"
        in: "query"
        description: "Product Id"
        required: true
        type: "integer"
      responses:
        "200":
          description: "OK"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Inventory"
      security:
      - basic: []

With this updated API, we can update the external service so that we can use it in a flow.

And with the updated API spec, we can automatically generate a stub method as well. From the empty stub method we can complete the function with the necessary logic to access Salesforce directly and update the Product object. Note that it uses the SFAuthService.js code from above and an API token to access the organization data.

Platform events and EDA

Now that this inventory method is available, we can check the operation with a simple flow that triggers on a Platform Event and updates the Product object. When we run this test flow, it updates the iPhone Product object in the organization.

How and when the flow might need to update the product inventory would be up to the actual business needs. However, triggering the flow to update the object can be done using Platform Events. The flow can respond to any Platform Event with a call to the Get Inventory method.

Deploy the business logic

The process described in this post can go on until the flow and app API converge on a stable design. Once stable, our programmer and low-code builder can complete their work independently to complete the app. The flow designer can build in all the decision logic that surrounds the flow and build out screens for the user to interact with.

Separately, and independently from the flow designer, the programmer can code each of the methods in the stub app to implement the business logic.

Summary

We’ve just started building out this app and running it as an external service. In this post, however, you’ve already seen the the basic steps that would be part of every development cycle: defining an API, registering methods that a flow can call, building out the stub app, and authorizing access for the flow, app, and Platform Event triggers.

Future posts in this series will take these basic elements and methodology to expand the flow to execute the business logic contained in the app via user interface elements for a complete process automation solution running entirely on the Salesforce Platform.

To learn more, see the Heroku Enterprise Basics Trailhead module and the Flow Basics Trailhead module. Please share your feedback with @SalesforceArchs on Twitter.

Source: Heroku