Explaining Clean Architecture In .Net Core (Breakdown & Example)

Clean Architecture is about architectural abstraction: Building out Simple, Maintainable, Flexible, Coherent Software

Arno Slabbinck
Dev Genius

--

Photo by Frans Ruiter on Unsplash

Introduction

“The first concern of the architect is to make sure that the house is usable, it is not to ensure that the house is made of brick”-Uncle Bob

Building/maintaining a complex software application where requirements always change is what makes software development the real deal. Software Design can either make or break your whole project. Imagine a scenario where you work on two different applications.

You have one software application where the software architecture looks like spaghetti — Every line of code is messy. You can’t easily separate where everything belongs; you can't make changes either, because you might break the whole thing.

You also have a software project but that looks like lasagna. Every layer is beautifully stacked on top of each other. You can clearly understand what each layer does, and you can easily test everything.

We can all agree that software architecture with a lasagna design is better.

That’s exactly what clean architecture represents. We design our software structure in a simple, easy to understand, and above all flexible kind of way.

We only focus on the essentials — we build what is necessary and optimize for maintainability.

These concepts are also closely related to YAGNI(You Aint Gonna Need It) and the DRY (Don’t Repeat Yourself)principle.

We don’t build to impress. We only want to meet our user’s needs/expectations. In doing so we avoid building unnecessary features or components.

For students coming out of college clean architecture is a whole different way of thinking. School teaches you to work with database-centric architecture(UI, Business logic layer, Data Acess layer) aka a typical CRUD application. The database is at the center of our application, and our application depends on it.

However in the business world we can’t afford rigidity. The customer always has different needs. Instead we focus on domain-centric architectures or practices and patterns of Domain-driven design.

I won’t bore you with all the practices and patterns of DDD. Instead I want to focus on Clean architecture. That’s the heart of DDD.

Our Clean architecture consists of different layers:

  • The Domain Layer(The models of what our application represents)
  • Application Layer(The use cases or how to solve the user problems. More commonly known as business logic)
  • Infrastructure Layer( A layer with external dependencies such as Repositories, and integrations to services (like external APIs, Stripe, etc.))
  • Persistence Layer(Different types of databases. We usually talk with databases with an ORM.
  • Presentation layer.

Important to note: Everything points inwards to the domain layer

Clean Architecture

We structure our application so that the domains are the core (Fundamentally we make sure our Core project is free of dependencies)

The domains of our system represent what the application will be about. We focus on the behavior of our application and the boundaries around them.

Our use cases(Application layer) solve the user's problems. Everything else can be thought of as a detail. Kinda like when you build a house— the rooms are the essentials, and the materials we use are the details.

So for example, the presentation layer is a detail. We can present our models with many different interfaces.

Also, our persistence layer is a detail since we can implement many other databases like NoSQL or SQL Server, MongoDB…

Every layer has a clear definition.

However, the clean architecture setup/building is only first step. Enterprise applications also need to survive for approximately ten years.

Most of our time is spend maintaining the system . That's why our application's most significant costs(60–80%) come from maintenance.

As wonderful as clean architecture is however, we don’t need to implement it everywhere.

When we use Clean architecture, we have abstractions to achieve loosely coupled and flexible software. The downside is we have more added complexity.

In Software development we don’t follow hard rules. We question when which tool, pattern or practice is the best to use depending on the context and business goals we have.

It’s best question things from a ground zero perspective. For example if we are only building an REST API application we don’t need to have 5 different layers. That would be overkill.

If however, we want to build an well functioning IOS app that can support many users, then using some Design Patterns/SOLID principles to handle complexity is important. It is all context dependent

I have added an extra link if you want to learn more about the SOLID principles.

Now I will show you a simple, clean architecture application to project structure when you want to build your next app.

Bringing It Together

fleet management

I will demonstrate how to set up clean architecture with a simple project.

I am making a simple Asp.Net Core application with a ReactJs front end called a fleet management app.

With the fleet management app, a company can manage its fleet. So the application can :
- Manage vehicles
- Manage fuel cards
- Managing drivers
- Scheduling maintenance and repairs

I have taken a single database CQRS pattern. CQRS stands for Command Query Responsibility Segregation. CQRS separates read and Write command (insert, update, delete) operations. It is mainly used for performance and scalability.

CQRS Pattern

Core Project

The Core Project

The Core Project is where all the business rules are. The entities encapsulate all the business rules. The entities know nothing of the other layers, and they don't use the names of any other classes or components in the outer layers.

I will be using a Drivers' Entity as an example.

public class Drivers : BaseEntity{   public string Name { get; set; }   public string Street { get;  set; }    public string City { get; set; }    public string PostCode { get; set; }    public string StreetNumber { get; set; }    public string Adres    {       get { return $"{Street} + {StreetNumber}, {City},       {PostCode}"; }    }    public Datetime Birthday { get; set; }    public string License { get; set; }    public string NationalRegistrationNumber { get; set; }    public bool Employed { get; set; }}

We should follow the Single Responsibility Principle for our entities(any other class for that matter). Our objects should have high "cohesion." The methods and properties should all be closely correlated with the purpose of the class. It's much easier to understand what the class does and maintain it.

For example, our entities don't have Data annotations like Required or MinLength. Data annotations support validation and help with Entity Framework map the entities into the relational model. The problem, however, with data annotations is that they cluster your domain.

If we were to change our data access technology(different DB), we would also have to change our entities because of our data annotations. So that's why I keep the data annotations out of it. With Guard Clauses and Fluent Validation, we can validate our models and separate validation into our application layer.

Application Project

  • Application Interfaces
  • View Models / DTOs
  • Mappers
  • Application Exceptions
  • Validation
  • Logic
  • Commands / Queries (CQRS)

We embed the use cases in the application layer and have our higher-level application logic or abstractions. The use cases are the interactions with the system, for example, getting a list of all the cars, and it's about how we can do things with our domain models.

In CQRS, we use separate pathways for our reads(queries) and writes(commands) to the database. What we ultimately try to do is organize our code into features. So a feature is an individual command or query or a use case. We don't have a layered architecture anymore.

You can use Mediator(Mediator pattern) to add additional behavior like logging, caching automatic validation, and performance monitoring to every request.

I won't go into detail on the CQRS pattern with Event sourcing. If you are curious, you can learn more here:

Remember that the application layer knows about the domain but not the other layers.

public interface IApplicationDbContext 
{
DbSet<Drivers> Drivers {get;set;}

DbSet<FuelCards> FuelCards {get;set;}

DbSet<Requests> Requests {get;set;}
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

Our flow of control goes from the presentation to the infrastructure layer. We implement the IApplicationDbContext via dependency injection. But the Implementation of the IApplicationDbContext lives in the Infrastructure Persistence layer. If we were to switch to a different database, we could easily swap it without influencing the application layer. The application layer only uses an abstraction.

We use the Dependency Inversion Principle for that very reason. The higher-level modules (Application Layer) Isn't dependent on the Lower level implementation(Persistence Layer)

Infrastructure Project

Everything related to I/O components belongs in the infrastructure layer. So that would be:

  • Repositories(Data access abstractions)
  • EF DbContext(ApplicationDbContext)
  • Web API Clients
  • Logging Adapters( are the translators between the domain and the infrastructure. For example, they take input data from the GUI and repackage it in a form that is convenient for the use cases and entities)
  • Email/SMS Sending

Since the things in this layer are likely to change, they are kept as far away as possible from the more stable domain layers. Because they are kept separate, it's relatively easy to make changes or swap one component for another.

Web project

  • MVC Controllers
  • Web API Controllers
  • Authentication / Authorisation

The Presentation Layer is the entry point to the system from the user's point of view. Its primary concerns are routing requests to the Application Layer and registering all dependencies in the IoC container. I use Automapper for DTO's (Data Transfer Objects) to transfer to a ViewModel to send it to the UI. We can use a Mediator to handle requests for DTOs by the Presentation Layer.

For Authentication and Authorization, we can use an IdentityServer implementation.

Takeaway

In case clean architecture is still unclear. I have added a small summary.

The fundamental principles of clean architecture are:

  • It’s really about the Separation of Concerns(Remember the lasagna structure. Every Layer is separated from the other)
  • Should be independent of frameworks(We can easily swap out a ReactJs framework for an Angular Front end)
  • They should be testable(We can quickly test code with unit tests and test every feature with integration test) Also known as TDD(Test Driven Development)
  • They should be independent of a UI
  • They should be independent of a database
  • The Clean Architecture DiagramCore: “Enterprise / Critical Business Rules” ( Entities ) — Application: “Application business rules” ( Use Cases ) — Next out: “Interface adapters” ( Gateways, Controllers, Presenters ) — Presentation: “Frameworks and drivers” ( Devices, Web, UI, External Interfaces, DB )
  • The innermost circle is the highest level(Our application evolves around our Domains)
  • Inner circles define interfaces(They determine what happens)
  • Outer rings Implement the interfaces(How something should be done)
  • Inner rings cannot depend on outer circles(Dependency rule: )
  • Outer circles cannot influence inner circles

I hope you have learned something. Happy coding :-)

Resources

--

--

Passionate software engineer about Code and Cloud. I enjoy simplifying life, love sharing ideas and boosting code productivity.