In the previous article, we encouraged making certain architectural decisions that can influence the final shape of application architecture. In this article, we will look at popular application architectures and how to create your own solution based on them. We will also identify the GRASP responsibilities presented in the first article within the layers of well-known application architectures.
Multilayer Architecture
The most popular multilayer architecture is the three-layer architecture, and its most notable representative is the MVC architecture or Model-View-Controller. MVC divides our application into three layers according to three responsibilities. Let's map them to responsibilities according to GRASP.
-
Controller: Responsible for coordinating workflow, serving as the Controller in GRASP. It may also be responsible for creating instances of Model objects, thus fulfilling the Creator function, and communicating with the database, which will be realized as Pure Fabrication. In MVC, the Controller is the entry point to the application.
-
Model: This layer contains data and behaviors that can represent business logic, making it an Information Expert equivalent. The Model can be either a rich or anemic entity. Often, database communication is handled within this layer (instead of at the Controller level). However, it may be a good idea to separate persistence from logic, even if our Model also serves as a database model.
-
View: Maps data from the model to views returned to the user, performed by Pure Fabrication services. Such a view can be a simple JSON object or a rich HTML view.
A simple multilayer architecture is beneficial in simple APIs where the backend application primarily serves to store and read data. In most cases, we will deal with such applications in simple API implementations within frontend frameworks.
Within the multilayer architecture, we have the flexibility to choose layers. We can often limit ourselves to just two layers, for example, a coordination layer and a business logic layer, where the view is part of the Controller's response. This especially makes sense if we use the previously mentioned CQRS or CQS. The Controller calls operations on the model, which returns nothing, and then queries the repository responsible solely for reading in the View layer.
Clean Architecture
Clean architecture is much more complex than multilayer architecture. It also has layers, although they are directed inward, unlike the layers in MVC. The workflow is from the outer layer to the inner and then from the inside out. At the same time, the direction of dependencies remains unidirectional inward through the proper use of programming interfaces (those interested should read about the "dependency inversion principle").
Clean architecture has three layers:
-
Infrastructure Layer: Often divided into two layers - ports and adapters, where ports are objects allowing entry into the application (e.g., HTTP controller), and adapters are entries to other devices (e.g., databases or other external services). The infrastructure layer is the outer layer, the only one interacting with the outside world, including dependent libraries or frameworks. This layer handles data reading (Query from CQS/CQRS) and writing. Objects in this layer fulfill Pure Fabrication and Creator responsibilities from GRASP.
-
Application Layer: A higher layer independent of the infrastructure layer. Its task is to coordinate specific use cases. It is independent of the framework and external libraries. Use cases (represented as services performing one specific task) fulfil the Controller's responsibility from GRASP. Typically, their role is to delegate tasks, fetch and save data to the lower layer and execute business logic code to the higher layer.
-
Domain Layer: This layer contains domain services and entities (usually rich) that perform operations on data and change the application's state. This layer is independent of persistence, which is delegated by the application layer to the infrastructure. Domain layer objects fulfil the Information Expert role from GRASP.
Clean architecture can effectively separate client business-related aspects from technical details. However, it is quite complex, making it worth using for more complex systems where we will benefit from it.
As can be seen, this architecture involves two models - a domain entity in the domain layer and a database entity in the infrastructure layer. Therefore, layers must map their models when transitioning to a higher layer or receiving data in the lower layer, increasing implementation overhead. This problem does not occur in multilayer architecture, where models are defined in the higher layer to which the lower layer has access.
What to Choose?
The appropriate architecture choice depends on the project's complexity and business requirements. Multilayer architecture is simpler to implement, while clean architecture provides better separation of responsibilities and is more scalable. Remember, architecture is like clothing - each project needs an individual approach to feel comfortable and work effectively.
Depending on our project's needs and complexity, we can adapt it. Clean architecture and multilayer (and others not mentioned here) are known ways to handle common issues such as maintainability, testability, and scalability. There is nothing to prevent us from modifying existing solutions and adapting them to our needs. A good idea is to identify the responsibilities defined by GRASP in our code and architecture. This way, deciding how to structure the code will be easier. Additionally, it is worth familiarizing yourself with other GRASP principles that can help us make decisions in grouping and separating responsibilities or prepare us for change.