@radekmie
By Radosław Miernik · Published on · Comment on Reddit
Picture this: you’re presented with a rather complex task. It took you some time to conceptualize it, and now you’re left with two reasonable approaches. Now, which one do you choose? If it feels familiar, it’s because it happens very, very often. Today, I’d like to argue that “Both.” is probably the best choice.
“But how both is even an answer!?” you say. Well, the complete answer would be “We go with A now and switch to B later, if needed.” I guess that also sounds familiar, though we often refer to it as MVP, alpha version, or just “v1”.
Now that we know we may need to switch to another approach down the road, how do we prepare for it? How can we make sure that such a change will be safe and feasible? Or even possible?
“A layer of abstraction?” you ask. Yeah, sure. But I’ll call it interface1.
When we’re talking about code, interfaces can be embodied in a set of rules. Depending on your programming language, it may vary in expressiveness and the level of usability, but the idea is the same – make the outside code ready for this new code to change its internals.
A good example, though relatively low-level, would be to think about some commonly used data structures, like a set. Countless implementations, each with a different performance characteristic. For example, a hash table set would have an O(1)
average but O(n)
worst-case cost, while a self-balancing binary search tree would have an O(log n)
cost (for most operations).
The point is that we’re not concerned with how it works precisely but whether it implements an interface we need. In the set’s case, the absolute minimum would be an option to check if it contains an element. Then, if we needed a dynamically extended set, a way to add and remove elements. A way to check if it’s empty or – more generally – how many elements it contains would be nice, too. With those in hand, we could already build a lot.
But how do we ensure that an implementation actually matches the interface? We could add unit tests. Ha, they could even be based on randomized values, so that we’d be sure they’re not value-dependent! Going further, we could add some property-based tests. Or go formal and actually prove that it does.
Being able to scale horizontally is worth aiming for. Sure, the vast majority of apps out there would fit in One Big Server, but that’s not the point! If you have some sort of API2 in your app, that’s the interface you’re thinking of.
The same goes for distributed queues (and their workers). The specific worker implementation is not important – the goal is to process all the tasks. Queues are inherently decoupled, so there’s a natural interface here. It also seamlessly handles an intermediate state, where multiple worker pools using different algorithms are running in parallel – either as a benchmark or an experiment.
Similarly, we could have multiple implementations exposing the same (HTTP) endpoints and use a load balancer on top. One step further would be Apollo Federation for GraphQL, where we abstract who resolves each field.
How could we translate the above ideas into user interfaces? My take here would be “same actions = same results”. So, if a Remove action was immediate so far but now would happen with a 5-seconds delay, it should be fine. Sure, it’d be great to indicate it somehow, but the result of this action is the same.
Another more intrusive example would be to replace a paginated list view with an infinitely scrolled one. The interaction has changed, but the user is still able to do what they did before. (I’m not saying it’s a good idea to do so, as I prefer pagination myself, but that’s just an example.)
I may have lost my train of thought along the way. My point still stands – it’s essential to know what the options are, even if we’ll go with the simplest one first. The details may change later, but the surroundings have to be ready.
You Aren’t Gonna Need It? Maybe.
My lecturer says that the most natural things are the ones that have many equivalent definitions. With that in mind, please excuse me in this text, as I’ll use the word interface rather broadly.
Yes, I know that API stands for Application Programming Interface.