From Chaos to Clarity: Unveiling the Secrets of Clean Architecture

Understanding Clean Architecture in C#

From Chaos to Clarity: Unveiling the Secrets of Clean Architecture

Introduction

Clean Architecture is a software architectural pattern that focuses on the separation of concerns, maintainability, and testability of an application. It is not tied to a specific programming language, but it can be applied to C# or any other language.

Core Principles:

Clean Architecture is based on several core principles:

  • SOLID Principles:

    Clean Architecture aligns with SOLID principles, especially the Single Responsibility Principle (SRP) and Dependency Inversion Principle (DIP).

  • Separation of Concerns:

    It emphasizes dividing the application into distinct, manageable parts.

  • Dependency Rule:

    Dependencies should always point inward toward the core.


Layers:

Clean Architecture typically consists of concentric layers:

Presentation Layer:

This is the outermost layer and deals with user interfaces, HTTP requests, and other external interactions. In C#, this could be an ASP.NET Core application or a WPF desktop app. It should be ignorant of the core business logic.

Example:

Imagine building a library management system, the Presentation Layer would be the web interface where users search for books, place holds, and check their accounts.

Application Layer:

The Application Layer is responsible for orchestrating use cases. It contains application-specific business logic. In C#, this might include application services and use cases (interactors). These use cases rely on the core domain models but don't know about external frameworks.

Example:
In the Application Layer, you'd define the business rules that govern borrowing, returning, and cataloging books.

Domain Layer:

The core of Clean Architecture is the Domain Layer. It defines the business logic and entities. In C#, this involves defining classes and structures that represent core domain concepts.

In some interpretations of Clean Architecture, the Domain Layer is considered to be part of the Application Layer. However, in other interpretations, the Domain Layer is considered to be a separate layer.

Domain Layer as part of the Application Layer:

In this interpretation, the Domain Layer is simply a logical grouping of the core business logic within the Application Layer. It is not a separate layer with its interfaces or dependencies.

Example:
Imagine a library management system. The Domain Layer might contain the following components:

  • Entities: Book, Author, Publisher, User, Loan

  • Value Objects: ISBN, Title, PublicationDate, Genre, Name, EmailAddress

  • Use Cases: CheckoutBook, ReturnBook, SearchBooks, PlaceHoldOnBook

  • Domain Events: BookCheckedOut, BookReturned

The Application Layer would then use these components to implement the business logic of the system. For example, the CheckoutBook use case might use the Book, Author, Publisher, User, and Loan entities to represent the book and user involved in the checkout process. It might also use the ISBN and Title value objects to validate the book and user input.

Domain Layer as a separate layer:

In this interpretation, the Domain Layer is a separate layer with its interfaces and dependencies.

Example:

Using the same example as above, the Domain Layer might contain the following components:

  • Interfaces: IBookRepository, IAuthorRepository, IPublisherRepository, IUserRepository, ILoanRepository

  • Entities: Book, Author, Publisher, User, Loan

  • Value Objects: ISBN, Title, PublicationDate, Genre, Name, EmailAddress

  • Domain Events: BookCheckedOut, BookReturned

The Application Layer would then use the Domain Layer interfaces to interact with the core business logic. For example, the CheckoutBook use case might use the IBookRepository interface to retrieve the book being checked out, and the IUserRepository interface to retrieve the user checking out the book.

Which approach to choose?

There is no one-size-fits-all answer to this question. The best approach will depend on the specific needs of your application.

If your application has a relatively simple business logic, then you may want to consider using the first approach, where the Domain Layer is simply a logical grouping of the core business logic within the Application Layer.

If your application has complex business logic, or if you want to have a clear separation of concerns between the business logic and the application logic, then you may want to consider using the second approach, where the Domain Layer is a separate layer.

Ultimately, the decision of whether or not to use a separate Domain Layer is a trade-off between simplicity and flexibility.

Infrastructure Layer:

The Infrastructure Layer deals with external concerns such as databases, APIs, and frameworks. In C#, this can include data access, external services, and specific technology implementations.

Example:

In the library system, the Infrastructure Layer would handle communication with the database using Entity Framework, as well as third-party services for book recommendations.

Persistence/Data Layer:

This layer is a subset of the Infrastructure Layer, responsible for handling data storage and retrieval, typically employing database repositories.

In short –

  • Responsible for data access and database interactions.

  • Contains repositories and data access codes.

  • Implements interfaces from the Core/Application Layer to perform CRUD operations.

Example:

If a user searches for a book in your library system, the Persistence Layer would fetch the relevant data from the database.


Dependencies:

Clean Architecture enforces the Dependency Rule, which means dependencies should always point inward. This ensures that high-level modules (like the Domain Layer) are independent of low-level details (like the database or UI framework). In C#, use dependency injection to achieve this.

Use of Interfaces:

Interfaces are a fundamental part of Clean Architecture in C#. They allow you to define contracts and enable loose coupling between layers. For example, define interfaces for repositories in the Domain Layer, and implement them in the Infrastructure Layer.

Testing in Clean Architecture

One of the most significant benefits of Clean Architecture is the inherent support for effective testing. With the clear separation of concerns and isolated components, unit tests, integration tests, and end-to-end tests become more straightforward to write and maintain. This results in a more reliable and stable application.

Benefits:

Clean Architecture provides several benefits, such as maintainability, scalability, and testability. It also allows you to switch out external components without affecting the core logic.


Below is an example of how a simple Clean Architecture might be structured in C#:

MyApplication/
    ├── Presentation/
        ├── MyProject.API/
            ├── Controllers/
            ├── appsettings.json
            ├── Program.cs
            ├── etc...
    ├── Infrastructure/
        ├── MyProject.Infra/
            ├── Data/
                ├── ApplicationDbContext.cs
            ├── Services/
    ├── Application/
        ├── MyProject.Application/
            ├── Interfaces/
            ├── Enums/
            ├── DTO/
    ├── Domain/
        ├── MyProject.Domain/
            ├── Entities/

How to set up project references?

When working with Clean Architecture in C#, you should set up project references in a specific order to adhere to the dependency rule.

Let us assume the following projects:

  1. Core Project (Domain Layer):

    This should be the innermost core of your application, and it should not reference any other project. It defines the entities (DB Models), and business rules.

  2. Application Project (Application Layer):

    The Application Layer should reference the Core (Domain) Project. It contains application-specific business logic and use cases, which rely on the domain models defined in the Core.
    For example: Interfaces, Enums, DTO's etc.

  3. Infrastructure Project (Infrastructure Layer):

    The Infrastructure Layer should reference both the Core (Domain) and Application Projects. It's responsible for implementing details like data access, external services, and framework-specific code. It depends on both the core domain logic and the application layer.

  4. Presentation Project (Presentation Layer):

    The outermost layer, such as a web application or UI, should reference the Infrastructure Project. The Presentation Layer should be kept as thin as possible to ensure separation of concerns.

Here's a visual representation of the dependencies:

  • Presentation Project -> (Referenced to) Infrastructure Project.

  • Infrastructure Project -> (Referenced to) Application Project and Domain (Core) Project.

  • Application Project -> (Referenced to) Domain (Core) Project.

By following this order, you maintain a clear direction of dependencies, ensuring that the inner layers remain independent of the outer layers. This promotes modularity and maintainability in your Clean Architecture-based C# application.

Conclusion

Clean Architecture in C# is a design approach that promotes a clear separation of concerns and dependencies to build robust, maintainable, and scalable software. By adhering to its principles and layering structure, you can create applications that are easier to develop, test, and maintain over time.

Thanks for reading, I hope this helps!!