Web Application Design: Architecture
Layered Architecture and Managing the dependencies
Less connections (Interfaces)
Adaptation of the Generic Pattern
Where to start?
In software applications, Architecture is the organization of highest-level design elements. Or according to Wikipedia:
The term software architecture intuitively denotes the high level structures of a software system. It can be defined as the set of structures needed to reason about the software system, which comprise the software elements, the relations between them, and the properties of both elements and relations.
Among the different architectural patterns, the Layered Architecture Pattern has emerged as the most widely used pattern, particularly for web applications. Most web applications use some kind of variation of this pattern.
The goal of Layered Architecture is to achieve Separation of Concerns (SoC) and modular design in a special and optimal way.
Let’s review some of the general properties of layered architectures.
Layered Architecture is a pattern that separates application concerns into separate modules called layers. In a layered architecture, modules are organized as layers in a stackwise fashion (in a logical sense), hence the name layered architecture.
Layered Architecture and Managing dependencies
In a system composed of modules, these modules have to interact with each other in order to achieve the goal of the system. These interactions will inevitably result in some sort of dependencies among modules. Dependencies, if not managed properly, can result in inflexible software (e.g. hard to modify, hart to extend etc) with tight coupling between modules. There are different ways for managing dependencies to avoid tight coupling. At the architectural level, one such way is to organize the modules into layered pattern.
|Layered architecture pattern is a type of modular design pattern, that is specifically aimed at reducing software complexity by reducing and simplifying dependencies – increasing module autonomy. Modules in layered pattern are arranged in a stackwise form, hence the name layered pattern.|
Let’s see how layered architecture pattern achieves this goal.
Lesser connections between modules
When two modules are connected or interfaced, it means that they are using one another’s services, or at least one of them uses the services of the other one. That is one module’s functionality depend on the service provided by the other module – a case of dependency. This situation, obviously, increases the chance for tight coupling between the modules, which makes it difficult to change one module independent of the other. Consequently, the more connections among the modules in a system, the more complex and less flexible the system will be. Hence, one of the goals of any good design strategy should be to minimize dependencies in terms of the number of connections between modules, thus reducing complexity and increasing flexibility.
For example, if there are n modules in a system, in the worst case, there can be n(n-1) dependencies between the module (counting boths sides of the dependencies). And in the best optimal case, only n – 1. In terms of number of connections (hence dependencies) between layers, Layered Architecture closely approximates this optimal limit.
Another important feature of layered architecture is that it allows only for unidirectional dependencies. That is, in a dependency relation between two modules/layers, only one of them depend on the other, typically higher layer depending on the lower one(s). For instance, the Service Layer (a higher level layer) depends on Persistence Layer (a lower level layer), but the persistence layer does not depend on the service layer. Ideally, it should not even know that service layer exists at all.
If on the other hand, two modules are both depending on each other (bi-directional dependency), in that case, if module A is changed, module B may need to be changed as well. But, changes in module B, may further require changes in module A, in circular way, making the task of modification difficult.
Without even seeing any concrete implementation or example, we probably agree that a unidirectional dependency is better – easier to manage – than a cyclic dependency.
Another feature of a layered architecture is that each layer knows only about the layer immediately below itself. Thus, if one layer is changed, it may only affect the layer immediately above it. For example, in the architecture shown below (on the right), if layer A is changed, it may only affect the layer above it, B, leaving layer C unaffected, which does not depend on A. While on the left design, if layer is changed, it may require both layers B and C to be changed.
This way of organizing modules allows for the parts of the application to be changed in a localized way, in isolation, without affecting other parts of the application.
Interface Injection is not a requirement of Layered Architecture. But, using with Layered Architecture usually results in a more flexible design.
Interface Injection is a technique for reducing coupling by making the dependent module depend on abstractions, not on implementation. The technique is implemented by injecting an interface between two modules. The diagram below shows going from a case of direct dependency to depending on an interface.
In general, a module’s implementation is a lot more likely to change than its interface, and Interface Injection makes use of this fact to reduce coupling of the dependent module to the service module. In this way, if implementation of Module B changes, Module A will be unaffected by this change.This rule can be applied at various levels of design: from designing larger modules at the architectural level, down to class level design. In fact, it is one of the important principles of object-oriented design called (SOLID).
Applying Interface Injection to layered architecture, we will get something like in the following figure, where a layer is composed of two sublayers: interface sublayer and implementation sublayer.
Interface sublayer consists of the set of interfaces (in the sense of Java interface) and the implementation sublayer consists of the set of concrete classes implementing those interfaces, plus other classes needed for the workings of the layer. For example, if this pattern is used with Data Access Layer (DAL), if at some point the data source changes and we need to change the implementation to reflect that change, the clients of DAL who depend on interfaces, will be totally unaffected by this change.
Layered Architecture for Web Applications
So far, we discussed layered architecture in a more general sense. Now, lets explore the adaption of this pattern for web applications.
For Java EE applications, a typical layered architecture may look like the following.
- Domain Layer (DL) encapsulates the business logic of the application.
- Service Layer serves as a mediator between the Web Layer and Domain Layer.
- Data Access Layer handles the task of saving and retrieving data to and from data source, while hiding data source access details from the rest of application. This allows the rest of application (the layers above it) to operate within their object-oriented world without concern about communication with the external data sources such as a relational database.
- Web Layer: front-end, responsible for handling user interactions: receives user actions and presents information to the user.
We will discuss each of the modules/layers with some details, dedicating a section to each.
The layered architecture shown above, nicely matches the stackwise formation of general pattern. But, a rearranged representation of it may be closer to the actual organization of the modules in a web application. This representation is shown in the figure below.
According to this model, more than one layer depend on Domain Layer. Although, this breaks the single-dependent-dependency rule, discussed above, this is a fact that stems from the nature of these applications. This topic will be discussed under domain layer heading.
This is the archicture used in the Quiz Website application.