The problem with tiered or layered architecture
Building applications out of tiers or layers offers a broad solution that developers generally find easy to understand. It promises a generic approach that can be applied to every use case and it fits neatly on a single PowerPoint slide. The problem is that it is too rigid a model to address the more flexible demands of larger, more distributed systems.
The evolving data challenge
Tiered architecture originally emerged as a means of scaling from client-server applications to internet-based solutions that could support hundreds of thousands of users. By placing a load-balanced presentation tier on top of processing logic you were able to handle peak load more effectively, provide a higher degree of resilience, re-use some code and make changes more quickly.
It worked, hence the fact that it has become so popular. However, it’s too inflexible to be an effective means of scaling for more modern low-latency applications where the data volumes are exponentially higher.
Tiered architecture is based on the fallacy that design can somehow be separated from deployment. This just does not work out in practice as a design based on layers says nothing about how processing should be distributed. Every request tends to follow the same route on its way to and from the database. The interfaces between these layers tends to be fairly chatty with data being passed around in small chunks. This does not lend itself to remote invocation, so layered applications often come unstuck when you try to distribute processing.
The end result are applications that are orientated around a centralised database server. Processing tends to be very inefficient, particularly if your tiers are running in separate environments. If you look at the actual work going on you may find that the majority of processing involves remote calls and data transformations rather than serving up business functionality.
The inflexibility of a generic solution
Tiered architecture presents a single abstract solution that tends to be applied in every use case. This is too much of a generalisation as a generic solution will struggle to adapt to different scaling and processing requirements. There will be times when all those layers feel like overkill while complex, long-running operations may require more involved infrastructure to manage.
Dividing a system into rigid tiers tends to undermine flexibility. For example, a tiered design may always dictate that validation always happens in the middle tier when there’s nothing wrong with deploying the same validation logic in both the presentation and middle tiers in more simple cases. More data-intensive logic may even be better situated closer to the data store. The point is that a solution should meet specific processing needs rather than conforming to an arbitrary abstraction.
A single processing route is likely to be too inflexible for most complex systems. You may want to partition your data and processes to make it easier to optimise specific areas separately. Data could also be brought closer to the presentation tier through caching mechanisms to reduce the distance that requests have to travel. None of this can be achieved easily through rigid tiers that cut across all your data and processes.
Defending the boundaries
Perhaps my biggest concern of tiered architecture is around the separation of concerns. This is often an issue with layered or tiered systems, but it does take a while to manifest.
The generic nature of components in a tiered application can make it difficult to define and defend clear abstractions. Tiers or layers tend to be demarcated by their technical role rather than business functionality which can make it easy for logic to bleed between components. Over time small functional changes will be introduced into each layer by time-pressured developers needing somewhere convenient to add fixes.
After a while it becomes impossible to tell where things are going wrong and minor feature requests necessitate code changes in every layer. De-coupling is never achieved and this becomes particularly acute once you start trying to add new applications in. Anti-pattern clichés such as the “big ball of mud” and “shotgun surgery” become every day realities.
Thinking in terms of services rather than layers
How do you break out of layers? Part of this is about mind-set. Considering a system in terms of layers and tiers as this encourages the development of generic components with vague ownership. A more service-based approach can help to provide greater flexibility where the system is broken down into self-contained, collaborating services with clear responsibilities.
This doesn’t mean that you can’t have layers, of course. Part of the fun of working with services is that you can do something different for each one. These services can be implemented as a set of deployable components or even a layered application. You should just confine each solution to the individual service rather than imposing the same pattern across the entire system.