Developers / Platform

Salesforce Integration Patterns – Making Integrations Easier

By Alex Crisp

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.

READ MORE: Ultimate Introduction to Salesforce Integration

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? 

READ MORE: Have You Considered AI-Powered Integration Testing?

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. 

READ MORE: What Makes a Good Test Class in Salesforce?

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!

The Author

Alex Crisp

Alex is CTO for Seven20, an ISV providing a CRM/ATS built on Salesforce. He has a wealth of experience building everything and anything on the Salesforce platform.


    Dances With Mules
    March 15, 2023 6:42 pm
    A whole article on integration patterns and no mention of using anything other than custom code, not even MuleSoft? If you're doing this any scale wouldn't it make sense to use a proper integration tool rather than knit your own?
    Nithesh K
    March 16, 2023 3:10 am
    I was just thinking, how good is if the request and response itself is larger chunk, considering clone the request multiple times can hit heap size ? I felt like return Type is missing in build Request method in ApiSrvice class
    March 16, 2023 4:21 am
    Informative blog. Thank you.
    Bob Phelps
    March 17, 2023 9:17 pm
    I'm doing an Oracle integration right now where I'm using Platform Events and Custom Metadata types to abstract the schema and data from the published event. The code that creates an event uses Custom Metadata Types to populate the fields of the Platform Event using a mapping on the mdt. Now I can manage any new data integration needs with records instead of code.
    Dev Support
    March 30, 2023 12:14 pm
    Informative article, Alex! Your explanation of Salesforce integration patterns is very clear and easy to understand. Your insights on selecting the appropriate pattern for each use case are particularly beneficial. It is a valuable resource for developers and integration specialists who are looking to make integrations easier and more efficient. Keep up the good work!
    Kritik Arrify
    June 30, 2023 5:22 am
    Great work, Alex! Your article on Salesforce integration patterns is exceptional. The way you explain each pattern is incredibly clear and easy to understand. I particularly value your insights on choosing the appropriate pattern based on specific use cases. This article is an invaluable resource for developers and integration specialists seeking to enhance their integration processes.

Leave a Reply