API Development using Clean architecture and facade design pattern in Asp.Net Core Web API

Introduction

In this article, I will implement the facade design pattern with clean architecture for modern API Development.

Facade Pattern

The Facade pattern is a structural design pattern that provides a simplified interface to a set of interfaces in a subsystem, making it easier to use. It involves creating a unified interface that wraps a set of interfaces in a subsystem, thus simplifying the overall usage of the subsystem.

Clean Architecture

Clean Architecture is a software design philosophy introduced by Robert C. Martin (Uncle Bob). It emphasizes the separation of concerns and the organization of code into concentric circles or layers. The key idea is to separate the core business logic from the external concerns like the user interface, database, and frameworks. This separation promotes flexibility, maintainability, and testability.

Clean Architecture Layers


Presentation Layer (Web API)

  • Controllers to handle HTTP requests.
  • DTOs (Data Transfer Objects) for communication.
// Sardar Mudassar Ali Khan
// CarCompanyController.cs
[ApiController]
[Route("api/[controller]")]
public class CarCompanyController : ControllerBase
{
    private readonly ICarCompanyFacade _carCompanyFacade;

    public CarCompanyController(ICarCompanyFacade carCompanyFacade)
    {
        _carCompanyFacade = carCompanyFacade;
    }

    [HttpGet]
    public IActionResult GetAll()
    {
        var carCompanies = _carCompanyFacade.GetAllCarCompanies();
        return Ok(carCompanies);
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var carCompany = _carCompanyFacade.GetCarCompanyById(id);
        return Ok(carCompany);
    }

    [HttpPost]
    public IActionResult Create([FromBody] CarCompanyDto carCompanyDto)
    {
        var createdCompany = _carCompanyFacade.CreateCarCompany(carCompanyDto);
        return CreatedAtAction(nameof(GetById), new { id = createdCompany.Id }, createdCompany);
    }

    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] CarCompanyDto carCompanyDto)
    {
        _carCompanyFacade.UpdateCarCompany(id, carCompanyDto);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        _carCompanyFacade.DeleteCarCompany(id);
        return NoContent();
    }
}

Application Layer

  • Interfaces for the Facade.
  • Implementation of the Facade.
// Sardar Mudassar Ali Khan
// ICarCompanyFacade.cs
public interface ICarCompanyFacade
{
    IEnumerable<CarCompanyDto> GetAllCarCompanies();
    CarCompanyDto GetCarCompanyById(int id);
    CarCompanyDto CreateCarCompany(CarCompanyDto carCompanyDto);
    void UpdateCarCompany(int id, CarCompanyDto carCompanyDto);
    void DeleteCarCompany(int id);
}
// Sardar Mudassar Ali Khan
// CarCompanyFacade.cs
public class CarCompanyFacade: ICarCompanyFacade
{
    private readonly ICarCompanyService _carCompanyService;

    public CarCompanyFacade(ICarCompanyService carCompanyService)
    {
        _carCompanyService = carCompanyService;
    }

    public IEnumerable<CarCompanyDto> GetAllCarCompanies()
    {
        try
        {
            var carCompanies = _carCompanyService.GetAllCarCompanies();
            return carCompanies.Select(MapToDto);
        }
        catch (Exception ex)
        {
            throw new ApplicationException("Error occurred while fetching car companies.", ex);
        }
    }

    public CarCompanyDto GetCarCompanyById(int id)
    {
        try
        {
            var carCompany = _carCompanyService.GetCarCompanyById(id);
            return carCompany != null ? MapToDto(carCompany) : null;
        }
        catch (Exception ex)
        {
            throw new ApplicationException($"Error occurred while fetching car company with Id {id}.", ex);
        }
    }

    public CarCompanyDto CreateCarCompany(CarCompanyDto carCompanyDto)
    {
        try
        {
            var newCompany = new CarCompany
            {
                Name = carCompanyDto.Name,
            };

            _carCompanyService.CreateCarCompany(newCompany);
            return MapToDto(newCompany);
        }
        catch (Exception ex)
        {
            throw new ApplicationException("Error occurred while creating a new car company.", ex);
        }
    }

    public void UpdateCarCompany(int id, CarCompanyDto carCompanyDto)
    {
        try
        {
            var existingCompany = _carCompanyService.GetCarCompanyById(id);

            if (existingCompany != null)
            {
                existingCompany.Name = carCompanyDto.Name;
                // Update other properties...

                _carCompanyService.UpdateCarCompany(existingCompany);
            }
            else
            {
                throw new KeyNotFoundException($"Car company with Id {id} not found.");
            }
        }
        catch (Exception ex)
        {
            throw new ApplicationException($"Error occurred while updating car company with Id {id}.", ex);
        }
    }

    public void DeleteCarCompany(int id)
    {
        try
        {
            var existingCompany = _carCompanyService.GetCarCompanyById(id);

            if (existingCompany != null)
            {
                _carCompanyService.DeleteCarCompany(existingCompany);
            }
            else
            {
                throw new KeyNotFoundException($"Car company with Id {id} not found.");
            }
        }
        catch (Exception ex)
        {
            throw new ApplicationException($"Error occurred while deleting car company with Id {id}.", ex);
        }
    }

    private CarCompanyDto MapToDto(CarCompany carCompany)
    {
        return new CarCompanyDto
        {
            Id = carCompany.Id,
            Name = carCompany.Name,
        };
    }
}

Domain Layer

  • Define the CarCompany entity.
// Sardar Mudassar Ali Khan
// CarCompany.cs
public class CarCompany
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Infrastructure Layer

  • Implement the repository, database context, and any other infrastructure concerns.
// Sardar Mudassar Ali Khan
// CarCompanyDbContext.cs
public class CarCompanyDbContext: DbContext
{
    public DbSet<CarCompany> CarCompanies { get; set; }

    public CarCompanyDbContext(DbContextOptions<CarCompanyDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure entity properties, relationships, etc.
        modelBuilder.Entity<CarCompany>().HasKey(c => c.Id);
        modelBuilder.Entity<CarCompany>().Property(c => c.Name).IsRequired();
        // Configure other properties...

        // Seed initial data if needed
        modelBuilder.Entity<CarCompany>().HasData(
            new CarCompany { Id = 1, Name = "Company A" },
            new CarCompany { Id = 2, Name = "Company B" }
            // Add other seed data...
        );

        base.OnModelCreating(modelBuilder);
    }
}
// Sardar Mudassar Ali Khan
// CarCompanyRepository.cs
public class CarCompanyRepository: ICarCompanyRepository
{
    private readonly CarCompanyDbContext _dbContext;

    public CarCompanyRepository(CarCompanyDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IEnumerable<CarCompany> GetAll()
    {
        return _dbContext.CarCompanies.ToList();
    }

    public CarCompany GetById(int id)
    {
        return _dbContext.CarCompanies.Find(id);
    }

    public void Add(CarCompany carCompany)
    {
        _dbContext.CarCompanies.Add(carCompany);
        _dbContext.SaveChanges();
    }

    public void Update(CarCompany carCompany)
    {
        _dbContext.CarCompanies.Update(carCompany);
        _dbContext.SaveChanges();
    }

    public void Delete(int id)
    {
        var carCompany = _dbContext.CarCompanies.Find(id);
        if (carCompany != null)
        {
            _dbContext.CarCompanies.Remove(carCompany);
            _dbContext.SaveChanges();
        }
    }
}
// Sardar Mudassar Ali Khan
// ICarCompanyRepository.cs
public interface ICarCompanyRepository
{
    IEnumerable<CarCompany> GetAll();
    CarCompany GetById(int id);
    void Add(CarCompany carCompany);
    void Update(CarCompany carCompany);
    void Delete(int id);
}

Dependency Injection

  • Register dependencies in Startup.cs.
// Sardar Mudassar Ali Khan
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ICarCompanyRepository, CarCompanyRepository>();
    services.AddScoped<ICarCompanyService, CarCompanyService>();
    services.AddScoped<ICarCompanyFacade, CarCompanyFacade>();
}

Conclusion

The implementation adheres to the principles of Clean Architecture in an ASP.NET Core Web API application, employing the Facade Pattern to encapsulate the complexity of the CarCompany CRUD operations. The presentation layer, represented by the CarCompanyController, handles HTTP requests, while the application layer introduces the ICarCompanyFacade interface and its implementation, CarCompanyFacade, responsible for orchestrating interactions with the underlying services.

The domain layer defines the CarCompany entity, capturing the core business logic, while the infrastructure layer handles data access through the CarCompanyRepository and database context. Dependency injection is used to wire up the various components in the Startup.cs file. This structured approach facilitates maintainability, scalability, and testability, allowing for easy extension and modification of the system in alignment with Clean Architecture principles. The provided code serves as a foundation, and further enhancements can be made based on specific project requirements.


Similar Articles