Design an interface for your component

May 19, 2021

It is time to write a thing or two about architecture and design. That seems like a more extended subject of discussion, thus I decided to put that into a mini-series. I will describe elements that most of the components, modules, and applications share and that you should definitely consider before jumping into the implementation.

The first and very significant element common in various modules you may ever implement is the interface. And that is the topic of today’s discussion.

What is an interface in your component/module/app

An interface is a contract that your component specifies, and which other components learn to be able to interact with your module. When you look at your component, in a matter of abstractions, it is usually the most outer layer, presented to the surrounding world. As a client, you do not need to learn every detail of its implementation but rather understand the general purpose and the things you can do with it. A properly designed interface allows its users for smooth integration with other elements of the system, and that’s why it is so important to do it well.

How to design a good interface

To understand what is an exemplary interface for clients to enjoy, you need to understand the spectrum of design decisions you are operating within. On the one side of that spectrum, there is an interface designed specifically for client’s use-cases. A component with such an interface hugely cares about its usage and optimizes its logic for those specific cases. On the other side of the design spectrum, there is an interface based only on its internal logic with a disregard for the clients’ use-cases. That kind of module focuses on a clean and optimal solution from its own perspective.

As you see, designing an interface is a battle between being caring and being selfish. The thing is, the best option is usually somewhere in between.

Caring vs. selfish

Let’s try to break it down, whether you should foster a caring component or a selfish one instead.

Well, considering the extremes, when your module is too caring, it may bring some advantages to a client, like a simple integration or backward compatibility; however, it also brings many pains. In the caring scenario, optimization may be complicated as users tend to have contradictory priorities, and handling particular use-cases complicates the interface. Maintenance becomes a nightmare very quickly because design focused solely on the consumers’ needs has less space for coherent architecture and good practices. Sooner or later, this turns out to be a costly approach.

On the other hand, when the component is very selfish and centered around its internal design, it will probably be cheaper to maintain, especially in the long run. But, there are also downsides on this side of the spectrum. From the client’s perspective, integration may be complex, and meeting business requirements could be hindered when the component’s interface does not allow for a specific use. A self-centered module may be a real pain for consumers trying to keep up with its changing interface.

Best architecture

The decision which approach to lean toward would depend on your specific case. You should consider the prospect of the system growth, your module evolution, and its consumers’ development. Think about the advantages of a potential interface and how you can tackle disadvantages.

Speaking about disadvantages, let’s compare the two approaches and try to find the sweet spot. When your interface is very focused on use-cases and does not standardize the input, it will become messy and difficult to modify. When users change, so should your interface, and there is no sound solution for that kind of strategy. Applying any standards later, as your codebase grows, will be more challenging.

Starting from the other side, and having a rigorous interface may be a better option if you can solve some of the issues your clients may experience. For instance, you may introduce a middle interface or higher-level abstraction to allow specific use-cases and mitigate integration costs. Of course, that will result in more code to sustain, but the situation is different due to a well-defined and strict interface. Better structured layers of abstractions make the maintenance of your component more predictable.

Conclusion

The bottom line is that it seems more profitable to plan your component interface based on its internal context and general use-case rather than to fully satisfy clients in their usages. It does not mean you should not care about consumers, but do not allow them to interfere with your module’s design. Remember that clients come and go, change from time to time; the interface should remain consistent.

Next week, I will show you some real-life examples to make this debate less academic. See you!