Ever since the beginning of my career (and that is a long time ago), Microsoft has been promoting layered architectures as the top level architecture for building web applications. Even today they still recommend to build modern web application in this very same way.
In the layered architecture recommendation it is advised to group code by technical concern, typically User Interface, Business Logic and Data Access, in such a way that each layer invokes the underlying one, under the guise of being able to reuse certain logic and thus save time.
Problems with the layered approach
The layered architecture approach comes with many problems though:
- All business logic is treated equal. Processing a million dollar order is using the same architecture and infrastructure as updating a product description. Clearly one of these is more important to the business and deserves a bit of extra attention.
- It is very hard to test the business logic. As all code ultimately depends on the lowest layer, usually the database and other infrastructure, all these dependencies will also be in place while testing. This makes testing slow, brittle and genuinly hard to do, if not impossible.
- It's also hard to scale from a capacity perspective. Once the app grows beyond the capacity available on the hosting machine, the only option you have is to split it at the layer boundaries and move the layers (now called tiers) to different machines. This however introduces a significant amount of latency (at least 2 extra network calls) to each operation.
- It's hard to scale from a development perspective as well. When changes or additions to the software are made, multiple team members will be touching the same layers, constantly getting in each others' way.
- Changes made to code in lower layers will have undesired consequences to upstream code that is 'reusing' it. This will lead to many bugs and unexpected behavior over the course of time.
The onion architecture
The testability concern can be addressed by introducing Dependency Injection and inverting the order of the layers. Basically putting the business logic as the lowest dependency and injecting the infrastructure (such as the database) into it.
This variant of the layered architecture is called 'Clean Architecture' today, but I will always remember it as the Onion architecture: When you peel away the layers, it will make you cry...
A better approach
Instead of grouping your code by technical concern, it is much better to group it by business capability. Put all the code for performing a given task together and treat it as a logical unit.
The benefits are:
- You can choose a different architecture for each capability depending on its importance. This could even be a layered architecture for simple things such as updating a product description, or a more complex one with higher tolerance to failure for processing that million dollar order.
- Each capability can be tested in isolation. It's architecture and design adjusted to the business logic complexity and testability requirements.
- Hosting capacity can be granted corresponding the the capability's needs, based on it's importance to the business and any seasonal differences. On Black Friday you want to provide more capacity to the order booking and payment infrastucture, while the update product description capability could be colocated with many other capabilities on a small machine somewhere in the corner of your datacenter.
- Also from a development perspective it's much easier to scale. Different team members can work on different capabilities in parallel without interfering with one another.
- This architecture also stands the test of time. Technologies and frameworks come and go, they change every few months if not faster. Business capabilities however stay stable for years.
What would you like me to cover next?
I understand that it is easier said then done to organize your code in a sliced way. Many difficulties arise when you try to do so, from problems when trying to compose a user interface from many capabilities to having a hard time finding the right service boundaries. Let me know in the comments what you struggle with most and I will try to cover it next...