The build vs buy debate comes up a lot on tech teams. In short, this debate is about whether the team should build a piece of functionality in-house or buy a third-party service, API, or library to fulfill the product need.

Lots of considerations factor in to the debate, but those are out-of-scope for this article. For now, let's assume you've decided to buy. What's the best way to proceed?

I claim that the most common pattern is actually an anti-pattern. It goes like this:

Manager: I've researched 10 options for the best Widget, and found that WidgetizerX is the best tool. I'll buy it now. Please implement it into the product.
Engineering Team: [Implements WidgetizerX into the product]

On the upside, this process is fast and does achieve the product goal. Unfortunately, it introduces tremendous technical debt. The implementation introduces a tight coupling between a third party service and your product.

If WidgetizerX goes out of business, or a better option appears in the future, or the decision to bring the functionality in-house is eventually made, the engineering team will either have to clear the technical debt first (by decoupling the implementation) or re-finance the debt (by way of a drop-in replacement) potentially at less favorable terms.

Here's the way it should go:

Manager: I've researched 10 options for the best Widget and decided on WidgetizerX. My next two choices were Windowmuxer and Panelista. I am most interested in features X, Y, and Z.
Engineering Team: [Researches APIs for all three, designs a lightweight abstraction layer for the functionality, implements a WidgetizerX driver]

Yes, this does slightly increase the cost of implementation. One could argue that the decision to buy was made in order to achieve the lowest possible implementation cost, and so spending the extra time to design an abstraction layer is stupid. My rebuttal is that a little bit of extra capital can eliminate a long-term interest-accruing debt.

Besides, it's comparatively cheap to do this. If an in-house implementation would take 3 months, and a drop-in implementation would only take a day, it's not that big a deal to spend two days on a driver- or adapter- based implementation. You've still saved 3 months in either case.

The adapter layer doesn't need to be complex; it just needs to support the common functionality of the different backends. If we're talking third party APIs, this layer might be as simple as an interface that defines authorization methods and the hooks and events that your application will tie into. Then you write an adapter around WidgetizerX and implement that.

My favorite software design principle is that of appropriate abstractions – "put things where they belong". When you buy a technology, it simply doesn't belong in a place where your product or business closely relies on it. It belongs a degree of abstraction away – behind a driver or adapter layer or something similar. Not implementing it that way will therefore accrue debt or cause other problems down the line.

I find that using this approach is worthwhile most of the time – that is, in most real-life cases, probably 80% of 'buy' technologies need to be replaced over the long term. Functionality moves in-house, or a different vendor is chosen, or the need to support multiple vendors arises. Sometimes you never need to use your adapter layer – maybe you stay on AWS S3 for file storage forever – but most of the time it pays off. Sometimes you get the adapter layer wrong and that introduces a different kind of debt, but teams get better at this with practice.

So if you do decide to buy, the best advice I can give you is to buy like you'd build: with research, a design step, and an implementation at the appropriate degree of abstraction.