Current understanding of the good software architecture evaluated over the past 20 years. Monolithic architecture is evaluated as a Multilayered and later so-called Clean Architecture.
But what really makes software architecture a good one? Good software architecture is a number of architecture decisions made and followed principles during the project lifecycle that describe architecture characteristics and structure.
Good architecture could be measured with such characteristics as scalability, maintainability, testability, usability, etc. Let’s see how Multilayered and Clean Architecture could satisfy those characteristics.
Before diving into Clean Architecture let’s see what Multilayered architecture offered and why it is evaluated as Clean Architecture.
Multilayered architecture is also known as N-Tier architecture offered many advantages in comparison to monolithic architecture. Let’s see some of them:
This architecture is quite good and its importance is hard to overestimate for the many big projects. However, there is one big problem with this architecture. It is the coupling of all the layers of the system. Transitive dependencies are still dependencies. The UI can’t function if business logic isn’t there. Business logic can’t function if data access isn’t there. The data access layer changes frequently. Historically, the industry has modified data access techniques at least every three years. If coupling prevents easily upgrading parts of the system, then legacy systems become stale, and eventually, they are rewritten.
It was first described by Uncle Bob as a Clean Architecture in 2012. There is a number of similar architectures with the same concepts:
•Vertical Slice Architecture
The main rule by which this architecture works is called the dependency rule. This rule states that source code dependencies can only point inwards. Nothing in the inner circle can know anything about anything in the outer circle. This means including functions, classes, variables, or any other program object declared in the outer circle must not be mentioned in the code in the inner circle.
Similarly, data formats used in the outer circle should not be used in the inner circle, especially if those formats are generated by the framework in the outer circle.
The center of the architecture is the Domain model, which is a combination of state and behavior that models the truth for the organization. There are other layers around the domain model with more behavior. The number of layers in the core of an application can vary, but the domain model is the very center, and since all communication is directed towards the center, the domain model is only connected to itself.
There are usually interfaces that provide the behavior of storing and retrieving objects, called repository interfaces. However, the persistence behavior of an object is not at the core of the application, as it is usually associated with the database. The core of the application is only the interface.
Normally Domain services layer contains the business logic specific to the domain. The application services layer contains services specific to the application logic. The main difference between these two types of services is that Domain services perform business logic over the core domain entities, but Application services perform logic specific to the application itself. So if you want to define a logic of how to handle some button click on the UI it might be Application services. If you want to define the rules of how to create an order in the system it might be a Domain service.
As an outer layer, we see the user interface, infrastructure, and tests. It is reserved for things that change frequently. These things should be intentionally isolated from the core of the application. Here we will find a class that implements the repository interface. This class is associated with a specific data access method, so it is outside the core of the application.
The architecture relies heavily on the principle of dependency inversion. The core of the application needs to implement the core interfaces, and if the implementation of these interfaces is in the outer layer, we need some mechanism to inject this code at runtime.
The database is not the center of the application. This is an external resource. Externalizing a database can be a big change for those people who are used to thinking of applications as "database applications". There are applications that can use a database as a storage service, but only through external infrastructure code that implements an interface that makes sense to the application core. Separating the application from the database, file system, etc. reduces the cost of maintenance over the application lifetime.
Now when we have an understanding of the Clean Architecture components let’s see the application solution structure using .NET. This is a simple web-service application that works with users. You can find a source code on GitHub.
First of all, let’s see the Core
project structure. It contains the Entities folder with the Domain models. Interfaces
folder with the definition of the interfaces required for the domain functioning. Services
folder which contains services to manage domain entity. Common
folder with some generic classes like exceptions, helpers, etc.
The Infrastructure
project contains the implementation of the database context interface to access the database and migrations to manage database changes.
Web API
project contains the REST service to access the user’s functionality. Here the UserModel.cs
contains the DTO class to transfer data from the client (Web API) to the domain.
All the dependency injection work is done in the Startup.cs
file:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {Title = "CleanArchitecture.Sample.Api", Version = "v1"});
});
services.AddSingleton(Configuration.GetSection(PostgresSettings.SectionName).Get<PostgresSettings>());
services.AddScoped<IUserService, UserService>();
// register application DB context
services.AddApplicationDbContext(Configuration);
}
Clean architecture is the evolution of the software application architecture which put the domain model in the center, but not the database as it was in an n-tier architecture. Also, the dependency rule is the main postulate of this architecture. Dividing the system into the layers makes it testable. When the database or any other service is out of date you can easily replace it.