The Exit You Never Designed
Two teams adopt the same third-party service. Three years later one of them migrates off it in a couple of weeks; the other concludes it’s impossible and stays forever, absorbing price increases and deprecations because leaving would mean rewriting half the product. The difference usually isn’t the vendor, and it isn’t how much either team used the service. It’s a decision made at integration time that neither team thought of as important: whether the dependency’s concepts were allowed to spread through the codebase, or whether they stopped at a boundary.
Spreading is the default, because it’s the path of least resistance. The library’s types become your function signatures. Its error classes get caught in your handlers. Its idea of what a “user” or a “document” is becomes your model, because translating felt like pointless ceremony when the shapes were nearly identical anyway. Every one of those is a small convenience and a small weld. Do it a thousand times over three years and the dependency isn’t something you use anymore — it’s part of your load-bearing structure, and the question “could we replace this?” has quietly become “could we rewrite the application?”
The alternative is a boundary you own: your own types at the edge, translation in one place, the vendor’s concepts not visible past that seam. It costs something real — an interface to define, mapping code to write and keep current, and the ongoing temptation to just reach through it when the wrapper doesn’t expose the one option you need today. That cost is why people skip it, and skipping is genuinely right for some dependencies. But the payoff is that replacement becomes a bounded project against a known surface rather than an archaeology expedition, and you’ll be glad of that on the day the vendor triples the price, gets acquired, or sunsets the API you built on.
Which dependencies deserve the boundary isn’t arbitrary. Ask how likely you are to want out — is this a commodity with several equivalent providers, or something so specific that switching means changing what your product does? Ask what leaving would cost if you had no choice, and how much of your critical path it touches. A logging library is fine welded in; a payments provider, an auth system, a hosted database, anything you might be forced off by pricing or acquisition — those earn the seam. And a useful reflex: notice when a vendor’s types start showing up in code that has nothing to do with that vendor. That’s the weld forming.
This is what ties the whole thread together. A dependency’s real cost isn’t the install or even the maintenance stream — it’s the option you gave up. A library you can remove in an afternoon is a tool; the same library, integrated everywhere, is a permanent constraint on your architecture and your negotiating position. The exit isn’t something you find when you need it. It’s something you either built at the start, when it was cheap and felt unnecessary, or didn’t — and by the time you need it, that decision is years old and no longer yours to make.