We technologists like layers of abstraction. If I had hundred dollars for every time I’ve heard something like “we’ll put a layer of abstraction around xyz component so that way if we need to swap it out, we can.” The decision is so habitual and frequent that often it washes over us without us realizing we’ve made a decision at all.
But putting a layer of abstraction over something is a decision. And it’s a big one.
I worked at a company once that put a layer of abstraction over JMS. So that we could, what, swap it out? Swap it out for what? JMS itself is, ding ding ding, a layer of abstraction that allows you to swap out your vendor implementation of messaging middleware. So, why in the world would you want another layer over it? There’s basically no upside and the huge downside is that every time you want to use a feature in JMS that’s not exposed by your abstraction layer, you have to first implement it in your layer (often imperfectly) and then finally you can use it.
This might be an especially bad habit in the Java space. Java developers are in love with abstraction layers, and especially in love with the concept of “swapping out” implementations. Only once in my whole career have I ever witnesses a subsystem being swapped out (Oracle for DB2 I believe), and in spite of many layers of abstraction (JDBC, a home-grown object-relational layer, and an application layer) it was still a lot of work.
But here’s what we’re often trading off without even thinking of it:
1) Performance. We are often trading off performance in a big way. Not only does an abstraction layer require that many more method calls, but it often prevents us from using the underlying database/cache/message bus the way it was intended to be used. There’s often a lot of string parsing and reflection. And we can’t use most of the advanced performance tweaks that the vendor built into their product–often the very tweaks that differentiate their product from the many others we might have purchased
2) Readability. Vendors have put a lot of thought into their API and have had many, many users vet it. Home-grown abstractions often don’t have the same amount of thought put into them. Or were not developed by people with experience developing APIs. Or were built for a specific purpose and over the years have been bludgeoned into uses they were not designed for. Often, using them requires many cartwheels and extra configuration steps and reflection.
3) Reliability. An abstraction layer is just one more place to introduce bugs
4) Debugability. An abstraction layer is just one more level of indirection, one more variable to keep in your brain when trying to figure out a bug.
5) Documentation. Home-grown layers are often poorly documented.