The Integration Sandwich
A delicious looking sandwich
API integrations seem to be one of those topics where everyone has their own special recipe. As a result a large application may have a variety of implementations and styles making code difficult to read and extend for other developers. However, there is a simple pattern that can be used for any integration, the “Integration Sandwich”.
The “Integration Sandwich” is where you “Build the Buns” and is basically three modules (or classes for those in OO) .
The modules/classes are:
Message/Request preparation (your code)
API call (not your code)
Response handling (your code)
The top bun, or layer, is for building out the message or request. Every API that I’ve worked with has generally needed some basic support to translate terminology, format, even human languages. This layer is where you go from your app “speak” into the API app “speak”. This layer also can set up the entire request.
Elixir code
Virtually all API requests consist of a URL, request method (GET, POST, etc), headers, and a body. There is authentication of various types in the header and sometimes the url. For the example above a request struct or object was defined and will allow for rigorous testing. Once built then the message or request gets passed on to the middle or meat of the sandwich.
The middle or “meat” layer is a simple HTTP/ API layer and out of control of local code. It basically passes the request to a function which then builds the request according to the HTTP client your app uses. There should be no testable code in this layer. If a full request object or struct is used there is flexibility to change out the HTTP client or use multiple HTTP clients for a single API. This middle or “meat” layer is where mocking happens.
Elixir code for an http client
Time for another food metaphor. Mocking is like salt, too much and you ruin your sandwich. I have seen apps mock every layer, including the db layer. This led to happy green tests but bugs and failures in production along with angry steakholders when the layers finally met. As a result, “never mock your own code” has been a staple of my diet in software development.
Elixir has behaviours which help create polymorphism and is a good way to define a contract to mock out the middle or HTTP client layer in tests and local development mode. Simply define a behaviour which satisfies your Client or “middle layer’s” needs and create a mock that satisfies that behaviour with a “happy path”. This mock can be used locally so developers in your organization can run the app locally and not have to make any external API calls. Automated tests can leverage this mock as well for most of the tests.
Elixir code for a client Behavour
Elixir code for a client mock
There are going to be areas where you may want to simulate an error from the API in your tests. There are good mocking libraries in Elixir, such as Mox, which leverage behaviours and allow you to pass in the simulated API error into your logic flow. The mock simply needs to be defined, put into the environmental variables, and finally given the desired response in the test.
The last layer of this sandwich is a response handler. This layer is where you receive the response from the HTTP Client or “meat layer” and translate it into whatever terminology or data your application needs. This layer usually involves logging errors from the client. Since this layer has just one responsibility, handling the response, it is easy to test and extend. Nothing in this layer needs to be or should be mocked.
Elixir code for a response handler
Your sandwich is now complete! These layers all follow “Single Responsibility Principle”, allow for responsible and minimal mocking, extendability, and rigorous testing.
Every API that I have worked with over the years across multiple countries, institutions, file formats, authentication schemes, and even human languages have all benefited from this “Integration Sandwich” pattern. Next time you need to build out an integration just remember integrations make you hungry and you need a sandwich.