Minimizing Switching Costs in Software Development
I’ve written previously about the dangers of high switching costs in software development. Being dependant on a third party is a huge risk. You are vulnerable to the whims of another company, including astronomical price increases. However, reliance on vendors is inevitable as they can significantly accelerate product development.
$500 a month for a third party SAAS sounds like a lot, but it is a lot cheaper than months of in house development. AWS can get your application in production for $50-$200 a month when a data center would start in the thousands of dollars. And of course, no one builds their own operating system, programming language, etc, though those tend to be free.
At some point, third party software has to be incorporated into our product. Avoiding all third parties is not realistic. Yet the dangers of high switching costs still exist so it is important to manage our integrations with third parties to reduce those switching costs.
The simplest case to talk about is a software library. These provide a very specific function so products are rarely based off of one. However, some libraries can be so convenient that we use that library in hundreds if not thousands of places in our code base. The probability of needing to replace a library is rare, but the cost is high. It can take many excruciating weeks to replace hundreds of calls in a code base. Find/replace only really works if you have a strong integration testing suite (unit tests obviously don’t help with replacing libraries). Without that suite, you need to manually replace and test each change.
The mitigation strategy here is to build a wrapper around that library. For every function you use from that library, you write your own function that simply calls it. This seems silly at first and is definitely annoying to do, but it is a minor annoyance in the scope of minutes that buys you insurance from weeks of work.
Integrations with third party APIs is generally a lot more complicated than using a library, but the principle of mitigation is similar. The logic for the third party API needs to be isolated in a wrapper. Unfortunately, third party APIs also come with third party data which may need to be stored in your system. This data also needs to be isolated well.
Let’s look at an example. We want to integrate some API to send texts. Let’s call this imaginary company Texting R Us, or TRU. Every text in TRU has an id assigned by TRU. If we send a message with TRU, TRU will give us the id they assigned. If one of our users responds to this message, TRU will call our webhook with the id of this message.
A very simple and direct integration would be to save these TRU ids in our database tables and reference them in our code whenever we need to retrieve the message. This would make it extremely difficult to switch to one of TRU’s competitors (e.g. Twilio) who may have different patterns for ids. In the initial migration to a competitor, none of the ids would match up so the replies can’t be synchronized with the original message. Not to mention, we’d have references to “tru_message_id” everywhere in the code base that we would have to change to “twilio_message_id”.
A better way to create this integration and protect ourselves from being locked into using TRU would be to create our own message ids in our system. Then we create a way to map our message ids to TRU message ids. If/when we need to switch to Twilio, we can create another mapping table for our message ids to Twilio message ids. The logic to check this new mapping is in our wrapper. The vast majority of our code base is unchanged.
I’ve obviously oversimplified things for the sake of clarity. Switching from one API to another is by no means easy or clearcut. But keeping an API integration isolated from the rest of your system will at least make it easier. Not isolating a dependency to an API will lock you into using the provider of that API because switching would be too costly. That provider could start providing a lower quality of service or raise prices well above equivalent competitors and you would still choose to use them. And even if there are no competitors today, that doesn’t mean there won’t be amazing ones tomorrow.
This is one of the hardest topics to tackle because the answers here are far from easy. I currently use AWS because I believe they provide a better quality of service. There are plenty of other providers for cloud infrastructure. Google Cloud has a far, FAR superior pricing model to AWS, which is why I used it years ago. Microsoft’s Azure is pretty popular and I’ve heard great things about Digital Ocean.
When I decided to switch from Google Cloud to AWS for my last project, it took about 2-3 days. In the span of software project time, that’s barely a blip for something that’s seemingly major. The reason this was quick was because I stick to the basics when it comes to infrastructure: raw computing instances, relational databases, and file storage.
Every competitive cloud provider provides these three things. Most apps won’t function without these three things so they get the heaviest usage, are the most reliable, and have the features to let you do what you need to do. There are a few other things that all providers have, such as queueing services and load balancers, but that’s beyond the point. The point is, I tend to not use any service provided by AWS or Google Cloud that I can not find elsewhere. That makes switching infrastructure easier, if not exactly easy.
That being said, we now go back to the point that avoiding vendors isn’t always feasible. Many cloud providers now also offer various machine learning or AI services. These are going to be based on that providers’ dataset and proprietary algorithms. This is going to make them less interchangeable than say a raw computing instance, which should function the same so long as it is running the same operating system. It’s possible that your product needs one of these services and you will have to depend on them to have a viable product. Unfortunately, I don’t have an answer for lowering switching costs here, but it is good to know that you’re taking a risk ahead of time.
Lowering switching costs is not free. I won’t even pretend that it’s easy. However, the effort spent in lowering switching costs is many order of magnitudes smaller than the effort needed to switch vendors if you have tightly integrated your system with that vendor. Spending time lowering switching costs is like buying insurance for that scenario. You don’t want to get into a car accident, but you’ll be glad to have insurance if that happens. You don’t want to switch vendors, but you’ll be glad to have spent time lowering switching costs if you do.