Efficient ASP.NET Core Web API Development with Clean Architecture, Flyweight Pattern

Implementing the Flyweight Pattern in an ASP.NET Core Web API with Clean Architecture involves organizing your code into layers and using the Flyweight Pattern to manage and share common data efficiently. Let's break down the implementation into different layers and provide a basic example. Please note that this is a simplified example, and in a real-world scenario, you would have more sophisticated logic and error handling.

1. Layers

Web API Layer (Presentation)

  1. Controllers: Responsible for handling HTTP requests.

Application Layer

  1. Use Cases: Contains business logic.
  2. Mappers: Maps between DTOs and entities.
  3. Interfaces: Define contracts for repositories.

Domain Layer

  1. Entities: Represent business objects.

Infrastructure Layer

  1. Repositories: Implement data access logic.
  2. DbContext: Entity Framework context.

2. Flyweight Pattern

In the context of a CarCompany CRUD, you might have a common set of properties (flyweight) that are shared among different instances of CarCompany. You can use the Flyweight Pattern to manage these shared properties efficiently.

Code Implementation
 

Domain Layer

// Sardar Mudassar Ali Khan
// CarCompany entity in Domain layer
public class CarCompany
{
    public int Id { get; set; }
    public string Name { get; set; }

    // Flyweight properties
    public FlyweightProperties FlyweightProps { get; set; }
}

// Sardar Mudassar Ali Khan
// FlyweightProperties class
public class FlyweightProperties
{
    public string SharedProperty1 { get; set; }
    public string SharedProperty2 { get; set; }
}

Infrastructure Layer

// Sardar Mudassar Ali khan
// CarCompanyRepository in Infrastructure layer
public class CarCompanyRepository : ICarCompanyRepository
{
    private readonly DbContext _dbContext;

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

    public async Task<CarCompany> GetByIdAsync(int id)
    {
        var carCompany = await _dbContext.Set<CarCompany>()
            .Include(c => c.FlyweightProps) // Include FlyweightProperties in the query
            .FirstOrDefaultAsync(c => c.Id == id);

        return carCompany;
    }

    public async Task<List<CarCompany>> GetAllAsync()
    {
        var carCompanies = await _dbContext.Set<CarCompany>()
            .Include(c => c.FlyweightProps) // Include FlyweightProperties in the query
            .ToListAsync();

        return carCompanies;
    }

    public async Task AddAsync(CarCompany carCompany)
    {
        _dbContext.Set<CarCompany>().Add(carCompany);
        await _dbContext.SaveChangesAsync();
    }

    public async Task UpdateAsync(CarCompany carCompany)
    {
        // Ensure FlyweightProperties are shared efficiently
        _dbContext.Entry(carCompany).State = EntityState.Modified;
        await _dbContext.SaveChangesAsync();
    }

    public async Task DeleteAsync(int id)
    {
        var carCompany = await _dbContext.Set<CarCompany>().FindAsync(id);
        if (carCompany != null)
        {
            _dbContext.Set<CarCompany>().Remove(carCompany);
            await _dbContext.SaveChangesAsync();
        }
    }
}

Application Layer

// Sardar Mudassar Ali Khan
// CarCompanyService in Application layer
public class CarCompanyService : ICarCompanyService
{
    private readonly ICarCompanyRepository _carCompanyRepository;

    public CarCompanyService(ICarCompanyRepository carCompanyRepository)
    {
        _carCompanyRepository = carCompanyRepository;
    }

    public async Task<CarCompany> GetCarCompanyByIdAsync(int id)
    {
        var carCompany = await _carCompanyRepository.GetByIdAsync(id);

        if (carCompany == null)
        {
            throw new NotFoundException($"CarCompany with ID {id} not found");
        }

        return carCompany;
    }

    public async Task<List<CarCompany>> GetAllCarCompaniesAsync()
    {
    
        var user = GetCurrentLoggedInUser(); 
        if (!user.IsAdministrator)
        {
            throw new UnauthorizedAccessException("Only administrators can access all car companies");
        }

        var carCompanies = await _carCompanyRepository.GetAllAsync();


        return carCompanies;
    }

    public async Task AddCarCompanyAsync(CarCompany carCompany)
    {
        var existingCarCompany = await _carCompanyRepository.GetByNameAsync(carCompany.Name);

        if (existingCarCompany != null)
        {
            throw new DuplicateEntryException($"CarCompany with name {carCompany.Name} already exists");
        }


        await _carCompanyRepository.AddAsync(carCompany);
    }

    public async Task UpdateCarCompanyAsync(CarCompany carCompany)
    {
        var existingCarCompany = await _carCompanyRepository.GetByIdAsync(carCompany.Id);

        if (existingCarCompany == null)
        {
            throw new NotFoundException($"CarCompany with ID {carCompany.Id} not found");
        }


        await _carCompanyRepository.UpdateAsync(carCompany);
    }

    public async Task DeleteCarCompanyAsync(int id)
    {
        var existingCarCompany = await _carCompanyRepository.GetByIdAsync(id);

        if (existingCarCompany == null)
        {
            throw new NotFoundException($"CarCompany with ID {id} not found");
        }

        await _carCompanyRepository.DeleteAsync(id);
    }

    private User GetCurrentLoggedInUser()
    {
        return new User { IsAdministrator = true }; 
    }
}

Explanation

  1. The GetCarCompanyByIdAsync method checks whether the requested CarCompany exists and throws an exception if it doesn't.
  2. The GetAllCarCompaniesAsync method demonstrates restricting access to certain users based on their roles (e.g., only administrators can view all car companies).
  3. The AddCarCompanyAsync method ensures that a new CarCompany has a unique name before adding it.
  4. The UpdateCarCompanyAsync method checks whether the CarCompany exists before updating it.
  5. The DeleteCarCompanyAsync method ensures that the CarCompany exists before deleting it.

Web API Layer

// Sardar Mudassar Ali Khan
// CarCompanyController in Web API layer
[ApiController]
[Route("api/[controller]")]
public class CarCompanyController : ControllerBase
{
    private readonly ICarCompanyService _carCompanyService;

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

    [HttpGet("{id}")]
    public async Task<ActionResult<CarCompany>> Get(int id)
    {
        var carCompany = await _carCompanyService.GetCarCompanyByIdAsync(id);

        if (carCompany == null)
        {
            return NotFound();
        }

        return carCompany;
    }

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

    [HttpPost]
    public async Task<ActionResult> Post([FromBody] CarCompany carCompany)
    {
        await _carCompanyService.AddCarCompanyAsync(carCompany);
        return Ok();
    }

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

        await _carCompanyService.UpdateCarCompanyAsync(carCompany);
        return Ok();
    }

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

In this example, FlyweightProperties represents the shared properties among different instances of CarCompany. The CarCompanyRepository in the Infrastructure layer is responsible for efficiently managing these shared properties using the Flyweight Pattern. The rest of the layers follow the Clean Architecture principles.

Conclusion

The implementation of the CarCompany CRUD operations within an ASP.NET Core Web API, following the Clean Architecture principles, has been successfully structured. The separation of concerns into layers such as Web API, Application, Domain, and Infrastructure promotes maintainability and scalability. Leveraging the Flyweight Pattern ensures efficient management and sharing of common data, particularly with the inclusion of the FlyweightProperties within the CarCompany entity.

The business logic embedded in the CarCompanyService layer provides a foundation for enforcing various rules and constraints, enhancing the overall robustness of the application. The examples include checks for entity existence, user roles, and uniqueness constraints during the execution of CRUD operations. These checks contribute to the overall reliability and security of the system.

Moving forward, it is important to adapt and extend the provided code snippets based on specific project requirements, ensuring alignment with the unique needs of the CarCompany domain. Additionally, thorough testing, error handling, and consideration of edge cases are vital aspects to be addressed for the production readiness of the application. Overall, this architecture lays a solid foundation for building a scalable, maintainable, and business-rule-enforced ASP.NET Core Web API.