Interpreter Pattern in ASP.NET Core Web API with Clean Architecture

Introduction

Implementing the Interpreter Design Pattern in an ASP.NET Core Web API with Clean Architecture involves several steps. Below is a simplified example demonstrating the usage of the Interpreter pattern for a CarCompany CRUD operation. in this article, I will demonstrate to you how we can implement Clean Architecture with Interpreter Design Pattern.

  • The application follows a Clean Architecture with layers: Presentation, Application, Domain, and Infrastructure.
  • The Interpreter Design Pattern is used to interpret queries for CRUD operations.

1. Define the Domain Model

// Sardar Mudassar Ali Khan
// Domain Layer
namespace Domain.Entities
{
    public class CarCompany
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

2. Define the Repository Interface

// Sardar Mudassar Ali Khan
// Domain Layer
namespace Domain.Interfaces
{
    public interface ICarCompanyRepository
    {
        Task<CarCompany> GetByIdAsync(int id);
        Task<IEnumerable<CarCompany>> GetAllAsync();
        Task AddAsync(CarCompany carCompany);
        Task UpdateAsync(CarCompany carCompany);
        Task DeleteAsync(int id);
    }
}

3. Implement the Repository

// Sardar Mudassar Ali Khan
// Infrastructure Layer
namespace Infrastructure.Repositories
{
    public class CarCompanyRepository : ICarCompanyRepository
    {
        private readonly AppDbContext _context;

        public CarCompanyRepository(AppDbContext context)
        {
            _context = context;
        }

        public async Task<CarCompany> GetByIdAsync(int id)
        {
            return await _context.CarCompanies.FindAsync(id);
        }

        public async Task<IEnumerable<CarCompany>> GetAllAsync()
        {
            return await _context.CarCompanies.ToListAsync();
        }

        public async Task AddAsync(CarCompany carCompany)
        {
            _context.CarCompanies.Add(carCompany);
            await _context.SaveChangesAsync();
        }

        public async Task UpdateAsync(CarCompany carCompany)
        {
            _context.CarCompanies.Update(carCompany);
            await _context.SaveChangesAsync();
        }

        public async Task DeleteAsync(int id)
        {
            var carCompany = await _context.CarCompanies.FindAsync(id);

            if (carCompany != null)
            {
                _context.CarCompanies.Remove(carCompany);
                await _context.SaveChangesAsync();
            }
        }
    }
}

4. Create the Interpreter

// Sardar Mudassar Ali Khan
// Application Layer
namespace Application.QueryInterpreters
{
    public interface IQueryInterpreter<T>
    {
        Task<T> InterpretAsync(Expression<Func<CarCompany, bool>> expression);
    }

    public class CarCompanyQueryInterpreter: IQueryInterpreter<CarCompany>
    {
        private readonly ICarCompanyRepository _repository;

        public CarCompanyQueryInterpreter(ICarCompanyRepository repository)
        {
            _repository = repository;
        }

        public async Task<CarCompany> InterpretAsync(Expression<Func<CarCompany, bool>> expression)
        {
            var result = await _repository.GetAllAsync(); 
            return result.FirstOrDefault(expression.Compile());
        }
    }
}

5. Implement Application Service

// Sardar Mudassar Ali Khan
// Application Layer
namespace Application.Services
{
    public class CarCompanyService
    {
        private readonly ICarCompanyRepository _repository;
        private readonly IQueryInterpreter<CarCompany> _queryInterpreter;

        public CarCompanyService(ICarCompanyRepository repository, IQueryInterpreter<CarCompany> queryInterpreter)
        {
            _repository = repository;
            _queryInterpreter = queryInterpreter;
        }

        public async Task<CarCompany> GetCarCompanyByIdAsync(int id)
        {
            Expression<Func<CarCompany, bool>> expression = c => c.Id == id;
            return await _queryInterpreter.InterpretAsync(expression);
        }

        // Implement other CRUD methods using the repository...
        
        public async Task<IEnumerable<CarCompany>> GetAllCarCompaniesAsync()
        {
            return await _repository.GetAllAsync();
        }

        public async Task AddCarCompanyAsync(CarCompany carCompany)
        {
            await _repository.AddAsync(carCompany);
        }

        public async Task UpdateCarCompanyAsync(CarCompany carCompany)
        {
            await _repository.UpdateAsync(carCompany);
        }

        public async Task DeleteCarCompanyAsync(int id)
        {
            await _repository.DeleteAsync(id);
        }
    }
}

6. Controller in Presentation Layer

// Sardar Mudassar Ali Khan
// Presentation Layer
namespace WebApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CarCompanyController : ControllerBase
    {
        private readonly CarCompanyService _carCompanyService;

        public CarCompanyController(CarCompanyService carCompanyService)
        {
            _carCompanyService = carCompanyService;
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var carCompany = await _carCompanyService.GetCarCompanyByIdAsync(id);
            if (carCompany == null)
                return NotFound();

            return Ok(carCompany);
        }

        [HttpGet]
        public async Task<IActionResult> GetAll()
        {
            var carCompanies = await _carCompanyService.GetAllCarCompaniesAsync();
            return Ok(carCompanies);
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] CarCompany carCompany)
        {
            await _carCompanyService.AddCarCompanyAsync(carCompany);
            return CreatedAtAction(nameof(Get), new { id = carCompany.Id }, carCompany);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> Put(int id, [FromBody] CarCompany carCompany)
        {
            if (id != carCompany.Id)
                return BadRequest();

            await _carCompanyService.UpdateCarCompanyAsync(carCompany);

            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            await _carCompanyService.DeleteCarCompanyAsync(id);
            return NoContent();
        }
    }
}

Conclusion

the provided example demonstrates the implementation of a Clean Architecture for an ASP.NET Core Web API handling CRUD operations for a CarCompany entity. The architecture is organized into layers Presentation, Application, Domain, and Infrastructure. Key design patterns, such as the Interpreter Design Pattern, are employed to enhance modularity and maintainability.

  • Domain Layer: Defines the core business entities, such as the CarCompany class.
  • Infrastructure Layer: Manages data access with the CarCompanyRepository, utilizing Entity Framework Core to interact with the database.
  • Application Layer: Implements business logic and services. The CarCompanyService orchestrates CRUD operations and utilizes the CarCompanyQueryInterpreter for complex queries.
  • Presentation Layer: The CarCompanyController handles HTTP requests, invoking methods from the CarCompanyService to perform CRUD operations.

Throughout the layers, the code is structured to follow Clean Architecture principles, promoting separation of concerns and maintainability. The use of the Interpreter Design Pattern in the CarCompanyQueryInterpreter facilitates flexible query interpretation.