I read this tweet:
"The object-oriented version of spaghetti code is, of course, 'lasagna code'. Too many layers." - Roberto Waltman— Programming Wisdom (@CodeWisdom) February 24, 2018
Jokes taken as advice
It's not the first time I'd heard of this quote. Somehow it annoys me, not just this one joke, but many jokes like this one. I know I should be laughing, but I'm always worried about jokes like this going to be interpreted as advice, in its most extreme form. E.g. the advice distilled from this tweet could be: "Layers? Don't go there. Before you know it, you have lasagna code..."
It's the same for every lame microservice joke out there; I feel most of those jokes cause people to reject any idea (no matter how good) related to splitting a monolith, using a service architecture (an architecture with services, not necessarily micro ones), etc. Like all things, it won't always be a good choice, but it won't be never a good choice either.
Actually, I think it would be awesome if people would more often consider the option to use some kind of event-driven service-based architecture. Just like I think it would be awesome if people would write more lasagna code, or layered code. Most attempts at layering fail horribly, just like many (most?) attempts at building services fail horribly. Why is that? Because mostly, we have no idea what we're doing. We do know our favorite frameworks by heart. Every configuration option, every little validation constraint, every command-line tool. But we don't know architecture that well.
As I've pointed out before, layers are indeed a great architectural tool for structuring your code. I won't repeat everything here. I just wanted to discuss the concept of a layer a bit more, because this tweet and my own response to it - "Actually, I've never encountered such a project." - received some interesting reactions, for example:
You haven’t? I have some to show you :D. Too many abstractions and services.— Jelrik van Hal (@jelrikvh) February 25, 2018
I think I have, but it's more about the layers of the call stacks at runtime than how the code is organised. Service A made up of X layers gets injected into service B, which is already made up of Z layers...— Dave Marshall (@davedevelopment) February 25, 2018
Indirection, not layering
This is actually what I was hinting at: layers, when applied successfully (I'll talk about what I mean by that), are a great thing for a code base. However, most attempts at layering actually result in (not layering, but) indirection. If you'd step-debug through unsuccessfully layered code, you'll taking many steps before you get to the clue of the joke: that all we're doing is bringing out some data, and putting it back in. The complicated steps hiding this simple truth, are the steps of a weird dance between controllers, action helpers, services, tables, data objects and mappers. A lot of indirection, due to mixing in infrastructural concerns at al levels, and often clumsy usage of the programming language.
Trying to trace the workings of this kind of code may feel like you're wading through sticky layers of lasagna. Or maybe, it feels more like peeling of layers of an onion. However, it doesn't mean we should get rid of the concept of a "layer". We only need to realize that what we're looking at really aren't layers.
Successful layering: define the rules
I think it only makes sense. We're trying to deal with the code we're looking at, trying to make it manageable. It's impossible to keep everything in our head at once. We try to follow rules like "skinny controllers", so we push code out of them. We want to prevent historical failures like having
mysql_query() calls all over the place, we don't want to mix PHP with HTML, and then this indirection ("too many layers" a.k.a. "unsuccessful layering") is what happens.
In my experience, successful layering can be achieved by taking care of the following:
We need to document the rules. Documentation doesn't have to be a document. It can be a directory structure, a naming convention, doc blocks above your code, etc. It can even be a rule set, verified by some static analysis tool.
We should reinforce the rules. This should be a team effort (so you're not that annoying "application layer" person). You should talk about how certain things may "belong to a different layer" and why. A common understanding will arise from this practice. Everybody should be able to explain why every rule is the way it is.
Instead of becoming zealous about the established rules, we need to re-evaluate them all the time. Is applying the rule still beneficial? Explain to each other what the merits of applying the rule is. Maybe it was a good idea a couple of years ago, but not anymore.
If you don't know where to start, just consider domain-application-infrastructure as a standard set of layers and try to follow the rules that come with these.
Keep in mind that you don't have to follow these rules in every imaginable situation. The rules are there to help you, and to evolve based on your experiences. You need to sometimes break them. For example, it's really hard (and in my opinion completely unnecessary) to implement the Specification pattern, without ending up with code that really belongs to several different layers. The same goes for your model classes; they will have certain characteristics that help them be persistable (which is an infrastructure concern).
Paradoxically, I find that it helps me make better decisions if I try to follow some rules very strictly. It'll help me understand why, when and how to break the rules responsibly. Just make sure that, whenever you feel like it's okay to break the rules, document (and reinforce, but also re-evaluate) this decision too.