On the Value of Interfaces (and when you Need One)

It is curious how people tend to bash DDD. I must admit - I never worked in a full-on DDD codebase or on a team that practices it, but looking at the mentioned articles like this one does make me shudder a little. There is little worse than a premature abstraction, and a there is a noticeable jump (or rather: a trough) which goes from abstraction to indirection. I’ve been programming for more than 20 years now - 12 of those professionally (with a little stint in-between) and I also went from obsessing over abstractions to a more, let’s say, “common sense” approach to them. Oddly enough, this is not about OOP for me - it is about modules. And, to an extent, types - but I do believe types and behavior are going to stay connected in meaningful way. Whether you do point.move() or move(point) is not of importance as long as it generalizes over some kind of Movable.

It did take a while for a more digestible take on this to begin to crystallize, so I fugured it could be put on paper.

SOLID principles are not as much about objects as they are about modules

The key in “getting” the SOLID principles is that they allow modules to be swapped for one another, within reason. Abiding by those principles make modules easier to swap. Not more, not less. There could be a discussion about “X or Y makes things easier to reason about”, but I find the “reason about” take to get wheeled out when one wants to bash another’s programming paradigm, and we’re not here for that. What this is about applies in equal measure to both the super-strict languages like Idris and to the wildest dynlangs like Ruby.

The principles allow us to make software that composes better.

Don’t overdo it

As in the example provided by the lone architect - let’s quote:

It’s fine for most simple use cases. It’s very readable, barely testable and it’s all in one place. It just does the job. But there’s many caveats :

Are there though? Is there anything else that needs to do things to the users table in the database? Why is this code “barely testable”? Is making a DBConnection available in the scope the function runs impossible? And of course it is perfectly testable - feed it a request and a response, make sure the request contains the two params, make sure the response got filled with a JS-object-ish thing having the properties id and emailAddress, make sure there is a row in the database. It just doesn’t need the modulization and the encapsulation and the interfacement smeared all over it in thick gobs. When it does become interesting to change it for the better though, is when (and only when!) additional requirements arrive - which are also preferably present not only within this function. For example:

Now thest two would be much better examples of what can change in this function. But if we need none of the above: “all in one place” is priceless. All in one place is easy to find, easy to read in one sitting, easy to scan for bugs, and you don’t have to jump between files or classes or what-have-you. All in one place is good. Do have things in one place.

Modules compose over interfaces

Your modules are like an AC power outlet and your appliances, and they compose (integrate) over the interface of a power plug. It is that simple. The presence of an interface makes sense if, and only if, there are multiple modules on either end of the interface and those modules can be swapped for one another. No multiple modules = no interface, YAGNI, KISS, don’t do it.

Defining interfaces

But what if you do need to swap implementations? Well, then your modules will integrate (compose, plug, connect…) over some kind of interface. Defining good interfaces is key. They should be small, so that they are easy to understand. An AC power plug is very easy to understand. They should not change without a big need to do so, or change within reason and allowing existing use cases. An AC power outlet with a ground socket is compatible with an AC power plug without a ground pin. Interfaces should be well-tested and well-communicated - a power outlet and a plug are well-specced items.

Coming from dynamic languages, I find that “enforcing” interfaces is very rarely justified. Every use case which is often excercised is going to reveal corner cases which are not covered by the existing interface. Instead of enforcing it (be it with language constructs like final, or with calling conventions like not allowing additional fields in a proto message or additional properties in a JSON object) define the minimum viable interface the caller needs to conform to. The smaller the interface, the easier it will be to conform to and to understand how it works. Even smallest interfaces will have corner cases - the Read() call in the io.Reader interface in Go has ambiguous behavior (is EOF an error or a read of zero-size?). A big interface makes a perfect breeding ground for corner cases big and small (did anyone mention ActiveRecord?)

Plugs and sockets, all the way down

An interface is just that - it is a set of rules by which two modules can interact with each other. The most obvious and familiar interface is the connection of the mains outlet in your office or apartment. The power outlet on the wall is a module. The plug on the end of the mains cord of your fridge is another module. The way by which these two combine to make a connection is the interface. The interface specifies a whole lot of interesting details:

Whether to have those interfaces (or contracts) in your system is the decision you need to make.

My beef however is this: DDD creates interfaces between the internal components of your system. A lot, a lot of them. Most of them will never be used to their potential, because you will never swap a UsersRepository for an Auth0Repository - and if you do, you will likely find that your interface is sorely lacking. The problem with interfaces is the same as with any other kind of process - you can’t design them in a vacuum. Creating the power outlet and plug took multiple iterations over multiple decades - and most importantly, the need for swapping modules at both ends of the interface was always present. Creating an interface for just this one login page to talk to just that one UsersRepository is nothing of the sort.

Do I actually need these layers and modules and interfaces and oh my?

So, hereby: rules of thumb for deciding whether you need an interface. I’ve followed this for quite a while, and they rarely failed me. At least when the climate was right.

Simple! Think about the answers to the following questions. You don’t have to tell me, because there might be a couple of spicy ones there. Just think for yourself. If most of the answers are “yes” - you might. If most of the answers are “no” - likely you won’t, or at least not in this stage of the system’ development (or - not in this stage of the development of your organization).

The more “yes”-s you have collected, the more reason you have to do layering/interfaces/modules in this particular scenario. If this made you doubt - the answer is likely a “no”. Don’t do modules, don’t do interfaces, don’t do architecture astronautics. Hexagonal, pentagrammatic or tetrahedric - just don’t.

Note that I am liberally sprinkling this with the words “right now”. If you do not need it now - you might very well need it in the future, but you will be designing your module and interface then, not now. By then, your system will have gestated and it is likely to scream at you where to introduce seams.

Seriously: if you do not need it now - you do not need the hexagonal architecture, and you don’t need the Repository pattern, and you don’t need the bounded context, and you might not need the Clean Architecture.

Applying the questionnaire to avoid premature layering

It is hard to slice a sandwich in thin slices - especially so if the sandwich is already thin. This article shows us a layering of a hypothetical Ordering microservice in an “Application layer”, “Domain model layer” and an “Infrastructure layer”. There is a number of alarms here suggesting this to be a questionable proposition. First: if your microservice needs 3 layers, is it really that “micro” to begin with? Second, let’s apply our questionnaire to that microservice.

Therefore: should the Ordering Microservice be layered? Judging from the article: hell no.

Interfaces and layering are cool, but once you actually have use for them. In other situations there is likely an entity trying to sell you a bridge.