Third-Party services are like the Borg. They want to assimilate your application into their ecosystems. After assimilation, removing them from your code base can be nearly impossible. Even if you can remove them, they often leave scars that will mar your application for the rest of its life.

Depending on the complexity of integration, they can become so painful to replace, you are either stuck with it or you must burn down a good portion of your code in the process of replacing it. The side effects can be horrendous. It’s this kind of moment where programmers curse the day they decided to take up the keyboard. Those who live by the keyboard, die by the keyboard. It can be a bloodbath.

Resistance is futile! Or so it might seem. But it doesn’t have to be that way. You can use Third-Party software in a way that avoids total assimilation. In fact, you can force Third-Party services to integrate with your application. Checkmate!

Third-Party Services

Just like every other enterprise-level company in the universe, Medely interfaces with a variety of Third-Party services to accomplish things that would not make sense to re-create internally. Using Third-Party services allows us to focus on solving problems more directly related to our customers’ needs. So it makes sense to avail ourselves of all the yummy, tempting, prettily packaged Third-Party services out there.

But buyer beware. There’s always a price to pay for all that Third-Party goodness. The biggest mistake you can make is to let the tentacles of those services get deeply integrated into the Application’s code base. Once that’s done, you’re now assimilated into their ecosystem.

Don’t let this tragedy happen to your application. Instead, make Third-Party services assimilate into your application. It can be done.

Expect Change

Any integrated service can disappear, have major updates, become unreliable, or no longer meet the application’s growing needs. Another brand spanking new service might have just been released that does everything 10X better. Whatever the reason, it’s pretty much guaranteed that any Third-Party service integration will eventually require something major to be done to them. When that happens, the change should be as painless as possible. So, code for change from the beginning.

Avoiding Assimilation

There are many ways an application can become assimilated by a Third-Party service. But they all boil down to allowing Third-Party services to integrate deeply into the application’s code base. However, it’s not just about avoiding entangling the service deeply into your code. Even if your code is well organized with great isolation, you can still get trapped in their ecosystem by adopting their way of thinking about the problem being solved.

Of course, it’s easy to let these services get tightly intertwined with the existing code base. They promise to make your life much easier, so why not marry into their hype? Unfortunately, once the honeymoon is over, even small changes can end up requiring multiple code point modifications. Where did it all go wrong?

It’s not all doom and gloom. People write great integrations all the time. Ideally, any Third-Party service should be easy to modify or replace. As always, there are many ways to get the same result.

The best ways will always isolate the service from the application’s code base and allow replacing it straightforward. Feel free to take this pattern and toss it into the intergalactic dust bin and do your own thing. Or better, twist it into something that makes you happy. That’s the fun of not being assimilated. You have options. Besides, this pattern isn’t new. But it’s a very good one for this problem (and others).

Principles

Isolation

All Third-Party interactions should be isolated from the code base it services and only interact at a thin API interface where the base code is on one side and the Third-Party code is on the other side (conceptually and/or by code organization). Now that’s pretty basic stuff.

Carefully Organize Your Code

To achieve isolation, it’s important to organize the service’s code in a way that places it entirely apart from the application’s code. Third-Party services are NOT part of the application. They are here to perform a job you don’t have the time or desire to code yourself. Keep them as far away from your application as possible and let them in only through specific API “doors” you designate.

Isolating the service’s support code should make it easy to identify and work on the integration code completely separate from the application’s code. Ideally, the integration code should live under a single folder structure completely behind the interface API. A change to the integrated service should not require a change to the application’s code.

The same applies to the application code. Changes to the application code should not require changes in the integration code. To achieve this, the application code and the Third-Party code must talk to each other through carefully defined boundaries, normally a single API layer.

The only time both the application and the Third Party integration code must change together is when the API interface changes. Even in this case, code isolation allows for the smallest set of changes.

The application should never be required to change its code to suit the service. The service is not the boss of the application. Don’t organize your code so that it becomes the boss.

Isolating services is like carefully separating the food on your plate behind WALLS! Those walls are the interface (API) between the services and your carefully crafted application. Don’t let the “food” intermingle. It may make food delicious, but it’ll give your application indigestion.

Craft Your API In Isolation From the Service

When integrating a service, It’s tempting to adjust your application to fit within that service’s way of “thinking”. Instead, you should think about how your application wants to ask for and receive the data for the problem you are solving. Initially, try not to let the Third-Party service inform your thinking.

Ideally, your API won’t simply reflect the service’s API. Instead, it should reflect your business needs in a way that makes sense for your application. At the very least, inform your ideas from the perspective of multiple services that accomplish the same thing. Regardless, making adjustments later is going to be easier if you properly isolate the service in the first place.

The Core Pattern

The core pattern is defined by three distinct layers: API Interface, Manager, and Adapter.

The implementation can be as simple as a single class or be broken into multiple classes (or whatever structure your language requires). But all three ‘layers’ exist within any integration. They must exist as separate parts in the code, even if you choose to use a single class.

The diagram below shows how you must go through each “layer” before you can get to the next layer whether from the Application side or the 3rd Party side. This is the Base Pattern at its simplest, and it is the key to enforcing strict isolation.

API Layer

This is the interface for the Application side. The Third-Party service doesn’t use this API. This is how the application will retrieve something from the service. This API is designed by and for the Application’s needs. This is how the Application ‘thinks’ about this problem.

Typically, this API (especially at initial implementation) will be informed by how the Third-Party service “thinks”, but the application should never twist itself to fit into the way the service thinks. If there is going to be any twisting, it should be to make the service meet your application’s needs. It’s far more cost-effective. Ideally, this API is the implementation of how the application wants to ask for and receive the data irrespective of how the Third Party does its thing.

Who are we designing this for? What do we want to accomplish? What’s the simplest API to achieve that? How do we want to ask for the data? What format do we want the data to be in when it’s returned? After all that, then consider how the service can inform your API along with a few of its competitors.

Every Third-Party service will likely accomplish the same thing differently. You don’t want to have to change your API to accommodate every service you might integrate. So, make them behave the way your application wants them to behave.

Pay the extra time now to save more time later.

Manager Layer

The Manager is the code that sits between the Application API Layer and the Adapter Layer and moderates the traffic between the application and the service. It enforces the API on the adapter and the adapter layer is squarely in its gaze. It defines the methods that the adapters must listen to and enforces how that data is returned to the API Layer.

It can exist (and often does at first) as part of the API Layer’s code, but as complexity grows, it can easily be separated into its own class. Just keep the API part separate from the Manager part. If load balancing is needed, this is where it’ll reside.

Adapter Layer

This is the code that encapsulates all direct interactions with the Third-Party service. Its primary purpose is to “adapt” the service to the application’s API. It maps the data it receives from the service into the expected format from the API.

It coerces the 3rd Party’s behavior(API) to work the way your application API Interface expects. In this way, you are forcing the 3rd party to be integrated with your application and not the other way around. It does this by making the Service play by the Application’s rules. Services are bent to the will of the application instead of the application bending itself to the will of the Service.

This layer can grow, like the Manager Layer, into multiple classes: one for mapping, one for talking to the service, and any other classes as needed. Trap the Third-Party in this layer. It will make all future changes much easier to accomplish.

Implementation Notes

The three layers described above are the base pattern. But how it’s implemented varies based on the complexity of what is trying to be accomplished. A simple service that provides very limited functionality doesn’t need the complexity of implementing the three layers separately. And complex implementations can break the manager and adapter layers into additional layers. It all depends on the need. The key is to keep the responsibilities of each layer separate and don’t let the application layer and the adapter layer intermix.

Key Advantages

Easier Refactoring

This is the main advantage and the key goal of this pattern. It makes replacing and refactoring these Third Party services easier and simpler. For instance, you can add a new Adapter to a new service without touching the existing Application and Adapter code.

Easier to Maintain

Code Isolation

With code isolated by Interface walls, refactoring, and bug fixes can be done with greatly reduced risk. The Application and the Service Adapters don’t care how each side does their business. Code can be changed at each layer without disturbing the other layers. As long as the adapter fulfills the requirements of the Manager, it will just work for the Application

The only time where a change will affect multiple layers is at the API Interfaces. Even in that case, the complexity of making changes is significantly reduced.

Changing the API Layer

APIs can change, especially at first. Services can come up with an entirely new version of their API and the Application may change how it “thinks” about the problem. Whether early on or later in the project, it’s helpful if such changes can be isolated in such a way as to reduce the risk of breaking the entire integration.

Below describes a major API change from the Third Party service side. But it can equally apply to a major API change on the application’s side.

In this scenario, the service has decided to release V2 of their interface and it’s a non-trivial change so the Application needs to re-do how they communicate with the service. Worse, they are going to sunset V1 in a year. Dealing with this is pretty straightforward within this pattern.

  • Create a new V2 adapter that implements the service’s new API
  • Adapt it to return the data in the expected format to the application’s API
  • Leave the old adapter in place while building the new adapter
  • When the V2 Adapter is ready, swap to using the new Adapter
  • Remove the old V1 Adapter

This isolates the change, reduces the risk, and requires no changes to the Application’s API layer. Best of all, you don’t have to change any of the application’s code. Well, unless the Application Layer wants to take advantage of new capabilities with the service’s new API.

The Drawbacks

The biggest drawback to implementing this pattern is that it typically takes more time to build. The more complicated the requirements, the more time it’ll probably take. Whether it’s beneficial timewise to use this pattern or not depends on answering the following questions.

  1. Will your application ever need to replace the Third Party service?
  2. Will your application ever need to do non-trivial refactoring driven by the needs of the application or the whims of the service?

If the answer to both questions is “no,” then this pattern isn’t going to provide as much. Its benefits will be limited to the general benefits of isolating code, which is non-trivial and could save you time in the long run anyway by making it easier to understand the implementation.

If the answers include any maybes, then you’ll probably benefit from using this pattern as it will allow you to adjust more easily as you get greater clarity. Start with the simplest implementation of the pattern that gets you what you need now. Your future self will thank you.

Conclusion

Third-Party services want to assimilate your application. It’s in their nature. Letting them intertwine deeply into your code base can easily make them a nightmare to update or replace later. Change is inevitable, but assimilation isn’t.

This pattern will let you prepare for the inevitability of that change. It may cost a little more time now to implement, but if done right, it will make changing the code surrounding your integrated services much easier. The extracost is relatively small but the payoff potential is huge.