If I had to guess, I would say that “Layers” is probably the most commonly applied architecture pattern. And why not? Parfaits have layers, and who doesn’t like a parfait? So layers must be good.
Like everything else, though, there’s a good way and a bad way.
The usual Neapolitan stack looks like this:
On one of my favorite projects of all, we used more layers because we wanted to further isolate different behaviors. In that project, we added a “UI Model” distinct from the “Domain.”
We impose this style because we want to separate concerns. This should provide us with two big benefits. First, we can change the contents of each layer independently. So changes to the GUI should not affect the domain, and changes to the domain should not affect persistence. The second benefit we want is the ability to substitute a layer. We may swap out a layer for the sake of testing (often in the case of persistence layers) or for different product configurations.
People sometimes make an argument for swapping out a layer in case of technology change. That argument is used for ORMs in the persistence layer, but I don’t find it convincing. Changing persistence on an existing application is by far not the most common kind of change. You’d be buying an expensive option that is seldom exercised.
When Good Layers Go Bad
The trouble arises when the layers are built such that we have to drill through several of them to do something common. Have you ever checked in a commit that had a bunch of new files like “Foo”, “FooController”, “FooForm”, “FooFragment”, “FooMapper”, “FooDTO”, and so on? That, dear reader, is a breakdown in layering.
It comes from each layer being decomposed along the same dimension. In this case, aligned by domain concept. That means the domain layer is dominating the other layers.
I would much rather see each layer have objects and functions that express the fundamental concepts of that layer. “Foo” is not a persistence concept, but “Table” and “Row” are. “Form” is a GUI concept, as is “Table” (but a different kind of table than the persistence one!) The boundary between each layer should be a matter of translating concepts.
In the UI, a domain object should be atomized into its constituent attributes and constraints. In persistence, it should be atomized into rows in one or more tables (in SQL-land) or one or more linked documents.
What appears as a class in one layer should be mere data to every other layer.
How Does It Happen?
This breakdown in layering can arise from more than one dynamic process.
- The application framework may impose this structure.
- The language may not have abstractions powerful enough to make it pleasant to work with data.
- TDD without enough refactoring. Each thin slice through the application adds one more strand of “Foo and Friends”. Truly merciless refactoring would pull out the common behavior sideways into the layer-specific concepts I described above. Lacking merciless refactoring, the project will accrete sticky strands like cotton candy on a toddler.
- The team may not have seen it done any other way.
What If It Happens To You?
Maybe you already have degenerate layers. Assuming they aren’t required by your framework, start looking for opportunities to refactor. Don’t just build a class hierarchy so you can inherit implementations. Rather, look for common patterns of interaction. Figure out how to turn the code you’ve got in classes into data acted on by classes relevant to the layer.
Use maps. Convert objects into maps from field identifier to an object that represents the salient aspect of the field for that layer:
- For a GUI, those aspects will be something like “lexical type”, “editable”, “constraint” / “validation”, “semantic class”, and so on.
- For persistence, they will deal with “length”, “representation format”, “referent,” etc.
Seek and destroy DTOs. They should be maps.
A DTO clearly indicates that your class is crossing a boundary. And yet, it requires that code on both sides of the boundary codes to the method signatures of the DTO. That means there is precisely zero translation at the boundary.
Where To Go From Here
Let me be clear, I like parfaits. (Yogurt and fruit! Ice cream, nuts, caramel!) I have nothing against layers. Most of my applications are built from layers. It’s just that getting the benefits we seek requires more effort than smearing a single domain concept across multiple subdirectories.
If “Layers” is the only architecture pattern you’ve used, then you’re in for a treat. There are plenty of other fundamental structures to explore. Pipes and filters. Blackboard. Components. Set GoF aside and go read Pattern-Oriented Software Architecture. The whole series is a treasure trove and an encyclopedia.