Clean Architecture in .Net Core using Automapper

Aasif Optisol
12 min readMar 27, 2023

--

Clean Architecture is an architectural pattern that emphasizes the separation of concerns and the use of loosely coupled components in developing software systems. It is a variation of the popular Clean Architecture pattern that was first introduced by Robert C. Martin, also known as “Uncle Bob”. The .NET Core implementation of Clean Architecture is specifically designed for building modern, scalable, and maintainable web applications using .NET Core and related technologies.

Clean Architecture is a design pattern that separates an application into different layers based on their responsibility. It is a way of organizing your code into independent, testable, and reusable components.

The primary goal of Clean Architecture is to create a structure that makes it easy to manage and maintain an application as it grows and changes over time. It also makes it easy to add new features, fix bugs, and make changes to existing functionality without affecting the rest of the application.

At a high level, the architecture consists of four layers:

Figure 1.0

Domain Layer: This layer contains the core business logic and rules of the application. It defines the entities, value objects, and business processes that are used by the application. The domain layer should be completely independent of any implementation details or infrastructure concerns and should be focused solely on representing the business domain.

Application Layer: This layer contains the business logic of the application and orchestrates the interaction between the presentation layer and the domain layer. This layer is responsible for implementing the use cases of the application and exposing them through a well-defined API. The application layer is also responsible for handling cross-cutting concerns such as caching, logging, and validation.

Infrastructure Layer: This layer contains the implementation details of the application, such as the database access code, external service integrations, and other infrastructure concerns. The infrastructure layer is responsible for providing concrete implementations of the abstractions defined in the other layers.

Presentation Layer: This layer contains the UI components of the application, such as the views, controllers, and models, which are responsible for presenting data to the user and accepting user input. In a web application, this layer typically consists of the front-end code written in HTML, CSS, and JavaScript, along with the server-side code that handles HTTP requests and responses.

Different between Micro-service & Clean Architecture:

Microservices and Clean Architecture are both software development concepts that aim to improve software design and development. While they share some similarities, they are fundamentally different in their approach and goals.

Microservices architecture is a style of software architecture that structures an application as a collection of small, independent, and autonomous services that can communicate with each other through APIs. Each service is designed to perform a specific business function and can be developed, deployed, and scaled independently. Microservices architecture aims to improve scalability, fault tolerance, and flexibility of an application.

On the other hand, Clean Architecture is an architectural pattern that focuses on the separation of concerns in software design, with the goal of creating a maintainable and extensible system. It is centered around the idea of organizing code into layers, where each layer has a specific responsibility, and dependencies flow inward towards the core. The architecture is defined by four layers: Presentation, Application, Domain, and Infrastructure. Clean Architecture aims to improve code maintainability, testability, and modularity.

Where to implement Clean Architecture?

Clean Architecture can be implemented in any software project, regardless of its size or complexity. However, it is most suitable for large-scale enterprise applications where maintainability and modularity are critical factors.

The implementation of Clean Architecture can vary depending on the specific requirements of the project, but generally, it involves the following steps:

Identify the core business requirements and entities: Before implementing Clean Architecture, it is essential to understand the business requirements of the project and identify the core entities and use cases.

Define the layers of the architecture: Once the core entities and use cases are identified, the next step is to define the layers of the architecture, such as the Presentation layer, Application layer, Domain layer, and Infrastructure layer.

Implement the layers: After defining the layers, the implementation of each layer can begin. Each layer should be designed to be independent of the other layers, with clearly defined interfaces and boundaries.

Write tests: Clean Architecture emphasizes the importance of testing, and each layer should be tested thoroughly using automated tests.

Continuously refactor and improve the architecture: Clean Architecture is a continuous process of improving the architecture by continuously refactoring and improving the codebase. Regular code reviews and refactoring can help keep the codebase clean and maintainable over time.

Can We implement Clean Architecture in Micro-service?

Yes, Clean Architecture can be implemented in a Microservices architecture. In fact, implementing Clean Architecture principles can help to improve the maintainability, testability, and modularity of each Microservice.

In a Microservices architecture, each service should have a clear and specific responsibility, and should be designed to be independent of other services. By applying Clean Architecture principles to each Microservice, developers can create a well-defined and maintainable codebase that is easy to modify, test, and scale.

Each Microservice can have its own implementation of Clean Architecture, consisting of the Presentation layer, Application layer, Domain layer, and Infrastructure layer. Each layer can be designed to be independent of the other layers, with clear interfaces and boundaries.

For example, the Presentation layer of a Microservice can expose an API that other services can consume, while the Domain layer can define the core entities and business logic specific to that service. The Application layer can orchestrate the interaction between the Presentation and Domain layers, and the Infrastructure layer can provide concrete implementations of the abstractions defined in the other layers.

Advantages of Clean Architecture

Clean Architecture has several advantages, including:

Improved maintainability: By separating concerns and organizing code into layers, Clean Architecture can make it easier to maintain and modify a codebase over time. Each layer has a clear responsibility, and changes to one layer should not impact other layers, making it easier to test, refactor, and maintain code.

Increased modularity: Clean Architecture makes it easier to create reusable and modular components that can be shared across projects. Each layer can be developed independently, and components can be swapped in and out without affecting other parts of the system.

Better testability: Clean Architecture emphasizes the importance of automated testing, making it easier to write unit tests, integration tests, and end-to-end tests. Each layer can be tested independently, and dependencies can be easily mocked or replaced, making it easier to write effective tests.

Clear separation of concerns: Clean Architecture provides a clear separation of concerns between the different layers of an application. This makes it easier to reason about the code and ensures that each layer only contains code that is specific to its responsibility.

Increased flexibility: Clean Architecture makes it easier to adapt to changing business requirements and to scale an application. Each layer can be developed independently, and the architecture can be modified without affecting the entire system.

Technology-agnostic: Clean Architecture is technology-agnostic, meaning it can be implemented using any programming language or framework. This makes it easier to switch technologies as needed, without having to restructure the entire codebase.

Disadvantages of Clean Architecture

Clean Architecture is a software development methodology that emphasizes the separation of concerns, modularity, and maintainability of code. However, like any software development approach, Clean Architecture also has its disadvantages. Here are some of them:

Steep learning curve: Clean Architecture is a relatively new approach, and developers need to learn and adopt its principles, which can take time and effort.

Increased complexity: Clean Architecture can result in increased complexity, as developers need to implement multiple layers of abstraction, which can make the code harder to understand and maintain.

Increased development time: Developing software using Clean Architecture can take longer, as developers need to write more code and spend more time planning and designing the system.

Requires experienced developers: Clean Architecture requires developers who have a deep understanding of software design principles and patterns, making it difficult for less experienced developers to work on the codebase.

May not be suitable for small projects: Clean Architecture may not be suitable for small projects or projects with a tight deadline, as it may be more beneficial to use a simpler approach.

Over-engineering: There is a risk of over-engineering with Clean Architecture, where developers focus too much on designing and implementing the architecture instead of delivering features and functionality that the user needs.

May not be compatible with legacy systems: Clean Architecture may not be compatible with legacy systems that are not designed using the same principles, making it difficult to integrate with existing systems.

Overall, Clean Architecture is a useful approach for building maintainable, scalable, and modular software systems, but it also has its disadvantages, and developers should consider these before adopting it for their projects.

Prerequisites:

  • Visual Studio 2022 with .NET 6 SDK
  • SQL Server Database

Clean Architecture Project Setup

1. Create Domain project

  1. Create a .Net core project with name of CleanArchitecture.Domain and name the Solution as StoreManagement.CleanArchitecture
Figure 1.1

2. Then Select .Net Core 6.0 in Framework

Figure 1.2

3. This is how CleanArchitecture.Domain project will look like

Figure 1.3

4. Create folder Entities

5. Create the file AuditEntity.cs inside the Entities Folder

namespace CleanArchitecture.Domain.Entities
{
public class AuditEntity
{
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
}

6. Create the file Product.cs inside the Entities Folder

using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace CleanArchitecture.Domain.Entities
{
public class Product: AuditEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; } = String.Empty;
public string Description { get; set; } = String.Empty;
public int Stock { get; set; }
public double Price { get; set; }
}
}

2. Create a Application Project

1. Create a new Class Library project in same solution and name it CleanArchitecture.Application

Figure 1.4

2. Install AutoMapper.Extensions.Microsoft.DependencyInjection.

3. Create the Folder name DTOs, Mapper and Repositories in CleanArchitecture.Application project

Figure 1.5

4. Create the Class name DependencyInjection.cs inside the CleanArchitecture.Application project

using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace CleanArchitecture.Application
{
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
return services;
}
}
}

5. Then Create the CreateProjectRequest.cs class inside the DTOs folder

using System.ComponentModel.DataAnnotations;

namespace CleanArchitecture.Application.DTOs
{
public class CreateProductRequest
{
[Required]
[StringLength(30, MinimumLength = 3)]
public string Name { get; set; } = String.Empty;

[Required]
public string Description { get; set; } = String.Empty;

[Required]
[Range(0.01, 1000)]
public double Price { get; set; }
}

public class UpdateProductRequest : CreateProductRequest
{
[Required]
[Range(0, 100)]
public int Stock { get; set; }
}

public class ProductResponse
{
public int Id { get; set; }
public string Name { get; set; } = String.Empty;
public string Description { get; set; } = String.Empty;
public int Stock { get; set; }
public double Price { get; set; }
}
}

6. Then Create file name ProductProfile.cs inside the Mapper Folder

using AutoMapper;
using CleanArchitecture.Application.DTOs;
using CleanArchitecture.Domain.Entities;

namespace CleanArchitecture.Application.Mapper
{
public class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<CreateProductRequest, Product>();
CreateMap<Product, ProductResponse>();
}
}
}

7. Now Create the File IProductRepository in Repositories:

using CleanArchitecture.Application.DTOs;
namespace CleanArchitecture.Application.Repositories
{
public interface IProductRepository
{
List<ProductResponse> GetProducts();

ProductResponse GetProductById(int productId);

void DeleteProductById(int productId);

ProductResponse CreateProduct(CreateProductRequest request);

ProductResponse UpdateProduct(UpdateProductRequest request);
}
}

3.Create a Infrastructure Project

  1. Create a new project CleanArchitecture.Infrastructure
  2. Install Microsoft.EntityFrameworkCore.SqlServer.
    Right click on CleanArchitecture.Infrastucture project / Add / Project Reference … / Check CleanArchitecture.Application / OK
Figure 1.6

3.Create a File in DependencyInjection.cs

using CleanArchitecture.Application.Repositories;
using CleanArchitecture.Infrastructure.Contexts;
using CleanArchitecture.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
var defaultConnectionString = configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<StoreContext>(options =>
options.UseSqlServer(defaultConnectionString));

services.AddScoped<IProductRepository, ProductRepository>();

return services;
}
}
}

4. Create the Folder Contexts and Persistence

5. Then Create the Class file name StoreContext.cs in the Contexts Folder

using CleanArchitecture.Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace CleanArchitecture.Infrastructure.Contexts
{
public class StoreContext : DbContext
{
public StoreContext(DbContextOptions<StoreContext> options) : base(options)
{
}

public DbSet<Product> Products { get; set; }
}
}

6. Create a new Class File name ProductRepository.cs in Persistence Folder

using AutoMapper;
using CleanArchitecture.Application.DTOs;
using CleanArchitecture.Application.Repositories;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Infrastructure.Contexts;

namespace CleanArchitecture.Infrastructure.Persistence
{
public class ProductRepository : IProductRepository
{
private readonly StoreContext storeContext;
private readonly IMapper mapper;

public ProductRepository(StoreContext storeContext, IMapper mapper)
{
this.storeContext = storeContext;
this.mapper = mapper;
}

public ProductResponse CreateProduct(CreateProductRequest request)
{
var product = this.mapper.Map<Product>(request);
product.Stock = 0;
product.CreatedAt = product.UpdatedAt = DateTime.Now;

this.storeContext.Products.Add(product);
this.storeContext.SaveChanges();

return this.mapper.Map<ProductResponse>(product);
}

public void DeleteProductById(int productId)
{
var product = this.storeContext.Products.Find(productId);
if (product != null)
{
this.storeContext.Products.Remove(product);
this.storeContext.SaveChanges();
}
else
{
// throw new NotFoundException();
}
}

public ProductResponse GetProductById(int productId)
{
var product = this.storeContext.Products.Find(productId);
if (product != null)
{
return this.mapper.Map<ProductResponse>(product);
}
return null;
//throw new NotFoundException();
}

public List<ProductResponse> GetProducts()
{
return this.storeContext.Products.Select(p => this.mapper.Map<ProductResponse>(p)).ToList();
}

public ProductResponse UpdateProduct(int productId, UpdateProductRequest request)
{
var product = this.storeContext.Products.Find(productId);
if (product != null)
{
product.Name = request.Name;
product.Description = request.Description;
product.Price = request.Price;
product.Stock = request.Stock;
product.UpdatedAt = DateTime.Now; //DateUtil.GetCurrentDate();

this.storeContext.Products.Update(product);
this.storeContext.SaveChanges();

return this.mapper.Map<ProductResponse>(product);
}

return null;
// throw new NotFoundException();
}

public ProductResponse UpdateProduct(UpdateProductRequest request)
{
throw new NotImplementedException();
}
}
}

Open Package Manager Console and select CleanArchitecture.Infrastructure project as default. Execute Add-Migration InitialCreate -Context StoreContext

In CleanArchitecture.Infrastructure project, a Migrations folder with 2 files inside were created.

Then, from the Package Manager Console, execute Update-Database.

Figure 1.7

After Migration the Database will Look like this:

Figure 1.8

4.Create a Presentation Project

1.Create productcontroller in the presentation project inside the Controller Folder

using CleanArchitecture.Application.DTOs;
using CleanArchitecture.Application.Repositories;
using Microsoft.AspNetCore.Mvc;
namespace CleanArchitecture.Presentation.Controllers
{
[Route("[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private readonly IProductRepository productRepository;
public ProductController(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
[HttpGet]
public ActionResult<List<ProductResponse>> GetProducts()
{
return Ok(this.productRepository.GetProducts());
}
[HttpGet("{id}")]
public ActionResult GetProductById(int id)
{
try
{
var product = this.productRepository.GetProductById(id);
return Ok(product);
}
catch (Exception)
{
return NotFound();
}
}
[HttpPost]
public ActionResult Create(CreateProductRequest request)
{
var product = this.productRepository.CreateProduct(request);
return Ok(product);
}
[HttpPut("{id}")]
public ActionResult Update(UpdateProductRequest request)
{
try
{
var product = this.productRepository.UpdateProduct(request);
return Ok(product);
}
catch (Exception)
{
return NotFound();
}
}
[HttpDelete("{id}")]
public ActionResult Delete(int id)
{
try
{
this.productRepository.DeleteProductById(id);
return NoContent();
}
catch (Exception)
{
return NotFound();
}
}
}
}

This is How the CleanArchitecture.Presentation project will Look like

Figure 1.9

and Configure the Appsetting.json file

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=******;Database=CleanArch;User ID=sa; Password=*****;TrustServerCertificate=True;"
}
}

now Complete the program.cs file in CleanArchitecture.Presentation

using CleanArchitecture.Application;
using CleanArchitecture.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

At Last make CleanArchitecture.Presentation as Startup project and then run the presentation project using Swagger

Figure 2.0

Now when we try to post the record in api

Figure 2.1
Figure 2.2
Figure 2.3

Now when try to get the record

Figure 2.4
Figure 2.5
Figure 2.6

Thanks for reading

Thank you very much for reading, I hope you found this article interesting and may be useful in the future. If you have any questions or ideas that you need to discuss, it will be a pleasure to be able to collaborate and exchange knowledge together

--

--