Introduction
Logical layers within n-tier systems should be designed to interact and be influenced by neighboring layers only. This restriction is often violated, which is detrimental to the system. In this article I discuss why this is so common, its consequences, and why we should focus on layer isolationism.
This article focuses on concepts and presents a detailed discussion. This article is built on the concepts that were discussed in "Dude, Where's my business logic?"
Physical tiers
For comparison purposes, let's first consider how physical tiers are related:
In this example a 3-tier system is represented. Each tier can only communicate with neighboring tiers.
Direct access from the client tier to the storage tier is not permitted because they are not neighbors. Most developers don't even consider trying such a hop. However in this article I will show you how developers perform the same offence with logical layers often without even recognizing it.
Tiers of layers
The terms tier and layer are often used interchangeably. When I use tier, I am referencing a physical layer. When I use layer, I am referencing a distinct software layer. Layers must exist on a tier, and thus tiers contain layers.
Layers are not bound to tiers. In fact in the next two diagrams you can see that the business layer can move between tiers in different deployment scenarios. Not all layers are mobile, but many are. Deployment scenarios depend on the network topology and other factors.
Business Layer on Storage Tier
Business Layer on Client Tier
Logical Limbo
For discussion I will use three layers, although many n-tier systems have more. Normally each of these layers has sub layers. Each layer should communicate and depend on its neighboring layer, and only its neighboring layer.
When placed on tiers, often at a high level it becomes a 1:1 relationship.
This is how we think about logical layers, and if only the interface connections are considered, this is an accurate depiction. With logical layers we need to consider not only the interface connection but also the interface design, influences, and other constraints. These are the phantoms of logical layers that are overlooked, or often not even recognized.
This is the most common implementation. The presentation layer has no physical connections to the storage layer, but the two have been designed around each other and tangible compromises have been made because of the constraints imposed by the other.
The situation becomes more apparent when the layers are repositioned to better show the relationships. Instead of a clear front to back system, it is clear that it is actually a circular system.
I have represented the layers in a high level overview. Each layer however has additional sub layers.
When typical logical links are added, you can see the clear violation of the "neighbor access only" rule.
In some cases, not only is the rule violated for logical links, but physical links are established as well that jump neighboring layers.
Such systems are extremely brittle and very difficult to upgrade, expand, or debug. For any layer to be worked on, a developer must be a "Santa Developer". That is, he should see everything, good and bad across the whole system.
By removing the tiers and rearranging the positions of the layers in the diagram, the pattern that has been created can be more easily seen. It's obvious that not only the layers are over stepped, but that a spider web is created.
Patterns
Storage Layer Driven
Using a storage layer driven pattern, the storage layer is first designed and then the presentation layer is designed around the storage layer. Once this is completed the business layer is designed around the storage layer, since the presentation layer has been designed around the storage layer. This causes artificial constraints in the presentation layer and limited transformations in the business layer. Result sets returned from the business layer are typically restricted to simple transforms that can be executed by SQL queries or stored procedures.
This pattern is extremely common because it is similar to the traditional client server development and to the systems designed around the existing databases. Because the presentation layer is designed around the storage layer, the presentation layer often mimics the actual structure of the data in the storage layer with unintuitive screens.
Very often there is an additional feedback cycle from the presentation layer to the storage layer. The feedback cycle occurs when something cannot be retrieved in a format convenient to the presentation layer. The developer then requests changes to be made in the storage layer for the benefit of the presentation layer, but which is detrimental to the storage layer. These changes are artificial and without such limitations is otherwise not necessary. These changes often violate or at least strain the proper precepts of database design, cause unnecessary data duplication and denormalization.
Presentation Layer Driven
Using the presentation layer driven pattern, the storage layer is designed around the presentation layer. Implementation of the business layer is typically fulfilled by simple SQL queries and does little transformation or isolation. Databases are poorly designed and suffer from performance problems as they were primarily designed for easy access by the presentation layer rather than using normalization and other storage layer concepts.
Isolation Driven
Using the isolation driven pattern, the presentation layer and the storage layer are developed independently, often in parallel. The two layers are designed without any influence from each other so as not to introduce artificial constraints or detrimental design elements. After these two layers are designed, the business layer is designed and it is the responsibility of the business layer to perform all transformations without requiring changes to the storage layer or the presentation layer.
Because the presentation layer and the storage layer are now completely independent, either can be changed as and when necessary by updating the business layer. Changes to two physically disconnected layers do not influence or directly impact the other layer. This allows for structural changes in the storage layer or presentation layer to quickly respond to the user needs without requiring system wide changes or updates.
Comparison
|
Storage Layer
Driven |
Presentation Layer
Driven |
Isolation
Driven |
Database |
- Can be well designed.
- Some negative compromises.
- Difficult to change storage layer as presentation layer is tightly bound to it.
|
|
- Poorly designed.
- Heavily denormalized.
- Not easily used by other systems.
- Performance issues under load.
- Difficult to change storage layer as presentation layer is tightly bound to it.
|
|
- Can be well designed.
- Focuses on storage, free of influences from the presentation layer.
|
|
Business
Requirements |
- Often does not meet business requirements.
|
|
- Frequently meets business requirements.
|
|
- Meets business requirements.
|
|
User Interface |
- Many compromises and presentation are structured around data, not the user.
|
|
|
|
Expandability |
- Generally expandable, but often requires significant reworking of user interface to accommodate changed data structures, or requires duplication of data in the storage layer.
|
|
- Integrated expansion difficult. "Cut and paste" functionality is common.
|
|
|
Partitioned Roles
Because old habits die hard, it can be very difficult for teams to adapt to the isolation driven pattern. To facilitate isolation driven pattern in teams the use of partitioned roles is very beneficial. Use of partitioned roles has other benefits as well, including faster and better development.
To prevent functions from being improperly distributed or placed in incorrect tiers, developers are assigned to a specific layer. The function of working on a specific layer is called a role.
Storage layer role
- Creation of views required by the business layer and as requested by the developer in the role of business layer.
- Creation of stored procedures for insertion, deletion and update of tables.
- Review and optimization of views and submitted SQL through plan optimization, index maintenance, etc.
Business layer role
- Creates and maintains external interface exposed to the presentation layer using web services or other remoting capability.
- Defines the external interface for use by the presentation layer.
- Requests views to retrieve and stored procedures to modify data from the storage layer role.
- Implements all the data transformations between the virtual and the physical layers.
- Creates initial regression tests for building and testing the business layer.
Presentation layer role
- Implements the end user interface.
- Connects the end user interface to the business layer using the interface provided by the business layer role.
- Designs and submits virtual datasets and or other data transport contracts to the business layer role.
Role Roaming
While a developer should be assigned to a specific role, developers should also roam among different roles.
Roaming among roles in smaller teams allows for better allocation of resources. Roaming in larger teams ensures that developers understand the whole system, understand the impacts of their implementations in neighboring layers, and ensures that no part of the system is affected if a developer becomes sick or leaves the company.
Roaming between roles however should not be at will and must be properly controlled. Developers should take up specific roles at specific times. If possible, a developer should not perform more than one role in the same module. That is if a system has a Customer module, a single developer should not be assigned to more than one role in the Customer module. However if a single developer works in the business layer role of the Customer module and then switches to the presentation layer role for the Vendor module, this is acceptable and allows resources to be more efficiently allocated.
This diagram demonstrates a proper example of role assignment.
This diagram demonstrates an improper assignment of roles. Both Joe and Adam have worked on two roles in the same module. Allowing such violations encourages influences and sacrifices between the layers.
The previous examples shown above were simple for demonstration purpose and only showed a single developer per module and tier. However, this is rare and in fact it's common for multiple developers to share the load. So long as none of the developers move to another tier within a given module, the guidelines are upheld.
In these examples, I have shown Pete as dedicated solely to the storage layer. This is not a rule, and in fact Pete can work in other layers. Other developers can also work in the storage layer. But typically the storage layer is handled by a DBA and only a DBA and this is why I have represented it this way.
Small Teams
In small teams focusing a developer on a given role will create resource gaps. In small teams, developers must be moved between roles more frequently as a role may be suspended when dependent items are completed. While moves are more frequent, they must still be controlled. The move from one role to another should be explicit and the move should take place only when there is no more work pending in the currently assigned role.
Tiny Teams and Lone Developers
In very small teams and with lone developers there is no choice but to work on multiple layers in a single module. Developers should remain on one layer at a given time and only make explicit moves between layers.
A common pattern is to build each module and move on to the next module only when it is completed. This approach is acceptable:
However the developer moves horizontally in 2/3rd of the moves. In each move if something arises the tendency is to take a step back and make a compromise, and then return:
It is often helpful to complete layer by layer instead of module by module. This isolates the layers from each other. This approach is however not always possible and requires a complete design beforehand. Some developers may also find this an uncomfortable approach and find it confusing to move sequentially between layers, but hop between modules repeatedly:
If this approach is used, note that even in this scenario two horizontal moves were performed:
If you use this approach, when moving between layers make sure to include a module movement as well. The following diagram demonstrates the best practice: