Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

Notes on The Clean Architecture

In this post, I’m going to share my understanding of clean architecture after reading about it in the book “Clean Architecture” and other posts, as well as how I implement the clean architecture in an ASP.NET core project.

What is clean architecture?

Clean architecture is an architecture in which the high level business rules and policies do not have a compile-time dependency on low level components that deal with external systems.

Consider the image below, which I take from the Clean Code Blog.

Uncle Bob’s The Clean Architecture

In the above image, the high level business rules and policies reside at the two innermost circles: Entities and Use Cases. The low level details reside at the outermost circle: Devices, Web, UI, External Interfaces and DB.

The above image is just a representative of a clean architecture. In practice, a project may have more or less number of layers, and the layers may have different names. For example, in the ASP.NET core application that I am working on, the solution consists of the following projects that correspond to the layers in the above picture.

  • Domain: Entity objects that encapsulate the business concepts. It corresponds to the innermost circle in the above picture.
  • Core – Business rules. It corresponds to use cases in the above picture.
  • Web API – Controller endpoints. It corresponds to controllers and gateways.
  • Infrastructure – Interface with database and other external system. It corresponds to the outermost circle in the above picture.

Using the above layers as an example, the domain and core layers make up the entities and use cases in my application. If you look at the arrows in the picture, the arrows point inward, from the outermost circle to the innermost circle. The direction of the arrows signify that the outer layers may know about (have dependency on) the inner layers, but not the other way around.

Why should entities and use cases not depend on the low level details?

In clean architecture, you want to keep the details, low level abstract away from the use cases. This allows you to replace the lower level implementations without affecting the use cases. To make this work, the higher layers should not have any dependency on the lower level layers. For example, the core layer knows nothing about the infrastructure layer. However, the infrastructure may depend on the core layer because it needs to aware of the use cases to implement the interfaces defined at the core layer. As such, in circular diagrams that demonstrate layers in a clean architecture, the dependency direction points inward, from the outer circles to the inner circles. The outermost circle represents the lowest, farthest away from the use cases layer. An application that properly follows the clean architecture is easier to maintain, reason and test. Because the use case layer does not depend on any external system, ideally, you can swap the infrastructure layer or change the implementations in the infrastructure layer without affecting the use case layer.

We do not expect changes in [use cases] to affect the entities. We also do not expect [use cases] to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. The use cases layer is isolated from such concerns.

The Clean Architecture

In my application, the infrastructure project has dependencies on the core and domain projects. Neither core nor domain have a dependency on infrastructure or web API, at least not at compile time.

How do use cases layer communicate with infrastructure layer?

At compile time, core and domain do not depend on infrastructure or web api. At run time, however, core and domain still need to communicate with infrastructure to interface with the database and external systems. The communication is indirect and via mediator objects. For example, in my application, core and domain communicate with infrastructure through the web api, which acts as a mediator.

Consider the code snippet below of the controller endpoint which the UI calls to retrieve the terms of use:

   [HttpGet("GetTermsOfUse")]
    public async Task<TermsOfUseDTO> GetTermsOfUse(int type) => await _mediator.Send(new GetTermsOfUseRequest((int)TouType.MediaAgreement == type ? _mediaAgreementOptions.Name : _disclaimerOptions.Name));

Several things happen behind the scene to retrieve the terms of use from the database.

  1. Web API communicates with use cases to process the request. The communication is indirect, via the MediaR library.
  2. MediatR invokes the registered handler to handle the request.
  3. The request ends up executing at the infrastructure layer, which hosts the handler.

One thing I want to point out is that the interface of the handler is defined at the use cases (core) layer. However, the implementation is at the infrastructure layer.

Had I call the handler’s implementation directly, I would have an unnecessary coupling between the web API and the infrastructure layer. This would not be consistent with the clean architecture because the web API is at the higher level than the infrastructure layer. In his post on clean architecture, Robert Martin states:

The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.

The Clean Architecture

In fact, note that the result of the method, TermsOfUseDTO is a POCO object which has no dependency on external frameworks. Following the idea of the dependency inversion principle and mediator pattern, the web API layer communicates with the use cases layer to process the request. At run time, execution ends up at the infrastructure layer, thanks to .NET dependency injection (DI) container which allows me to set up implementations for the interfaces at runtime.

Below code snippet shows the interface of the handler, which is at the use cases layer. Note that besides extending from IRequest which is part of MediatR, GetTermsOfUseRequest consists only of a string property. This is consistent with the principle that the high level policies do not have a source code dependency on low level details.

namespace Core.Requests
{
    public record GetTermsOfUseRequest(string TouName): IRequest<TermsOfUseDTO>;
    
}

Below is the implementation, which is at the infrastructure layer.

 public async Task<TermsOfUseDTO> Handle(GetTermsOfUseRequest request, CancellationToken cancellationToken)
        {
            var termsOfUse = await _dbContext.TermsOfUse
                                   .Where(row => row.Description == request.TouName)
                                   .AsNoTracking().FirstAsync();
            return _mapper.Map<TermsOfUseDTO>(termsOfUse);
        }

In the above snippets, the LINQ expression returns an entity object. However, this object is not suitable for returning to the UI, as the entity is a model of a table row in the database. Therefore, I convert the result into a POCO object.

 public class TermsOfUseDTO
    {
        public int Id { get; set; }
        public string Content { get; set; }
        public string DisplayName { get; set; }
        public string Description { get; set; }
        public DateTime CreatedDate { get; set; }
    }

How is clean architecture different than n-tier architecture?

An n-tier architecture can be open or closed. In an open n-tier architecture, a layer can call any layer below it. In a closed n-tier architecture, a layer can only call the layer immediately below it. As an example, if I were to implement n-tier architecture in my application, the use cases layer, or a web API could have a dependency on the infrastructure layer. To my understanding, this would be less flexible because changes to the infrastructure might have a direct impact on the use cases.

As we have seen, in a clean architecture, the use cases and entities do not have a source code dependency on low level details.

References

The Clean Architecture

“The Clean Architecture” Clean Architecture: A Craftsman’s Guide to Software Structure and Design, by Robert Cecil Martin et al., Prentice Hall, 2018.

Clean architecture vs onion architecture

Mediator pattern

MediatR

Dependency Injection in ASP.NET core

Notes on the Dependency Inversion Principle

No comments yet