There comes a point in every Salesforce Developer’s career where they are tasked with integrating an external system into Salesforce in some way. If you’re lucky, you can find an AppExchange solution that matches your requirements, however if you’re integrating within some niche, or have very specific business requirements, chances are the AppExchange solution will just cause more problems than it solves. It’s in these times we need to build a custom integration using Apex.
Just like anything else built on Salesforce, requirements change and evolve over time, and if your integration isn’t built in a way that can easily support this, it can be a source of a lot of tech debt and frustration. But why is this?
Salesforce Integration Patterns: Trigger Points, Logic, and Integration Services
Integrations are often built based on the requirements a business has – for example, when an opportunity is closed, a queueable class is invoked which gets the line items and sends them over to an external system via a HTTP callout. This seems like a logical approach – and it is. However, a common pitfall is to fail to encapsulate the different aspects of the integration, resulting in a rather monolithic class, or set of classes, that are difficult to maintain.
Let’s take a step back and look at our integration; we want to identify the different areas of our integration and where their boundaries are. These are usually:
- Trigger Points: When do we actually need to call the external system
- Business Logic: How we process our data to meet the business requirements
- Integration Service: The code that actually interacts with the external service via HTTP callouts.
Identifying these different aspects and respecting their boundaries is the first step to making integrations easy. It allows us to maintain each aspect of it separately – encapsulating it and allowing changes in one area to only affect that specific area.
For example, if a version of the API we are using is being upgraded or deprecated, we could update our integration service without having to make any major changes to the other areas of the system. This is also true when the business requirements of the integration changes or if our integration service is stable; we only need to update and adjust those specific areas of the code, while the integration service code can remain untouched.
We call this Separation of Concerns (SoC) and is a good principle to follow regardless of what you’re building – the benefits of which become very clear when we apply it to an integration where we necessarily control all the pieces in play.
Now that we’ve identified our different areas, let’s look at building them.
Building Integration Services for Salesforce
Before we begin building out our integration service, let’s take the same approach we did previously, looking at the different aspects of it, as well as where and how they interact.
- Requests: What we actually ask the external service to do
- Responses: What the external service gives us back
- HTTP Service: Actually performing the HTTP callout
- Settings Handler: Managing access of different settings such as base URLs, and providing authentication secrets, etc.
- Authentication Handler: Something to handle the authentication to the external service
- Schema: Classes built to provide the outline of the data’s format.
First up, let’s take a look at our requests and responses. These form the basis of how we interact with the API, which we use whenever we wish to communicate with the API. You may be thinking: but each request wants different payloads – how can we handle that?
The short answer here is inheritance. The longer answer is all of our request classes will extend a generic request class. We do this so that we can have a unified entry and handling of our requests, simplifying the process of creating new requests to be simple creating a new class, which extends our BaseRequest class.
The above is a simple version of our base class. As you can see, it has a few abstract methods, final variables, and a constructor defined. Let’s talk about the purpose of each:
- Constructor: This constructor takes in two arguments: the first being the HTTP method of this specific request, and the second is a System. Type variable: this should be the type that can handle the response for this request – something we’ll cover in more detail later.
- Abstract Methods: These are our generic methods that can be used to turn our easy to use request class into something that can actually be sent across the internet in a HTTP request. Since the URL and the request body varies depending on the specifics of an API and a request, we leave the handling of these to our concrete implementations.
This lets us define the logic for building our payload to where it’s needed, and provides a unified method for our HTTP handling logic to get the details for our requests. This also has the added benefit of meaning that if a request’s body changes, we only need to update the code related to that specific request – once again reinforcing our separations of concerns.
We can now take a deeper look into our APIs documentation and build out all of the request classes we need. Plus, because they’re all separate, it can be a good idea to also implement additional methods to further streamline the process of setting it up; so when we wish to invoke a request, it’s as simple as giving it the context and letting it handle the details.
The specifics of how to do this really depends upon the API in question, but the bare minimum required is providing the implementations to the abstract methods and calling the super constructor.
Now that we have our requests, the next logical step is to produce our responses for these requests. Again, the exact requirements for these classes depend upon the API, so we’re going to go over the generic structure that will allow us to easily build these out as required. Simply put, our response should take in the HTTP response and parse it into something that can be easily consumed by our business logic.
There’s a little bit more to it than that however: we can also use this to handle errors returned by the API. Let’s take a look at the generic abstract class.
Again we have another abstract class, but this time it has a single public virtual method and 2 abstracts. Our handleResponse method – the virtual method – is the entry point for our class, and this is called passing in the raw HTTP response. It then figures out whether or not the response was successful or not.
We have this method as virtual as not every API is the same, as sometimes they always return a 200 response code so having this method as virtual allows us the flexibility to customise that as required.
The other two methods in this class are used to define the specific behavior for the request – since that behavior varies drastically by API endpoint, we define its behavior in the concrete implementation of the request. Yet in essence, this method should be used to parse the response into some Apex types which are easy for you to use within your basic logic, and adding some methods to access these is advisable. You can even add some methods to assist in error handling, such as determining whether the request should be replayed in the event of an error.
Next comes our service class. This class is how we actually handle turning one of the above requests into a response – by building our HTTP request and setting it up as expected. This is usually aided by some secondary classes, a settings class (used to retrieve stored settings for the API such as base endpoint URL), and one that may or may not be used depending on the authentication required, which is an authentication handler. Splitting these classes up allows us to encapsulate their specific behavior, and provides a great place for us to mock their behavior during tests, making them easier to write and maintain as they could evolve separately.
Looking at the above class, one thing is immediately apparent: we only have a single public method and this method takes in one of our generic request types, and returns a generic response type. This is how we perform requests, and since we are simply passing in a single parameter, this makes it super easy for us to consume it within our business logic.
All that’s required is to build our concrete request type and to pass it into our service, and viola! A HTTP request has been performed and we have easy strongly typed access to our response, which is easy for us to work within our business logic. No more having to fight with JSON and building requests within our business logic!
While a lot of the above could be copied into your own code base and repeated for an easily scalable integration service, it’s really worth first taking a step back to examine what exactly is required from an integration. Break it up into the business serving code, and then the non-business code.
We do this to make it easier for us to centrally and easily respond to changes in an API, and it means that when that client requests just one more bit of information from that API, you can rapidly scale it by simply creating two additional classes without having to touch any other code and the risks associated with that. This can be applied to any problem you encounter but integrations are a prime example as the boundaries are a lot more apparent. Next time you’re having to work with an external service, give the above approach a go, and let me know how you found it!