DEV Community

Cover image for 🧱 Lesson 2C - Implementing Repository Interfaces (Domain & Infrastructure Layers).
Farrukh Rehman
Farrukh Rehman

Posted on

🧱 Lesson 2C - Implementing Repository Interfaces (Domain & Infrastructure Layers).

Series: From Code to Cloud: Building a Production-Ready .NET Application
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead
LinkedIn: https://linkedin.com/in/farrukh-rehman
GitHub: https://github.com/farrukh1212cs

Source Code Backend : https://github.com/farrukh1212cs/ECommerce-Backend.git

Source Code Frontend : https://github.com/farrukh1212cs/ECommerce-Frontend.git

Introduction

In Clean Architecture, the Domain Layer contains only business logic and abstract contracts (interfaces), while the Infrastructure Layer handles data access and interacts with databases, APIs, or external services.

Repositories bridge the domain and infrastructure layers:

  • Domain Layer: Defines what data operations are possible (interfaces).
  • Infrastructure Layer: Implements how these operations are executed (concrete classes).

Domain Layer: Repository Interfaces

IProductRepository
Path: ECommerce.Domain/Repositories/IProductRepository.cs

using ECommerce.Domain.Entities;

namespace ECommerce.Domain.Repositories;

public interface IProductRepository
{
    Task<Product?> GetByIdAsync(Guid id);
    Task<IEnumerable<Product>> GetAllAsync();
    Task AddAsync(Product product);
    Task UpdateAsync(Product product);
    Task DeleteAsync(Guid id);
}
Enter fullscreen mode Exit fullscreen mode

ICustomerRepository
Path: ECommerce.Domain/Repositories/ICustomerRepository.cs

using ECommerce.Domain.Entities;

namespace ECommerce.Domain.Repositories;

public interface ICustomerRepository
{
    Task<Customer?> GetByIdAsync(Guid id);
    Task<IEnumerable<Customer>> GetAllAsync();
    Task AddAsync(Customer customer);
    Task UpdateAsync(Customer customer);
    Task DeleteAsync(Guid id);
}
Enter fullscreen mode Exit fullscreen mode

IOrderRepository
Path: ECommerce.Domain/Repositories/IOrderRepository.cs

using ECommerce.Domain.Entities;

namespace ECommerce.Domain.Repositories;

public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id);
    Task<IEnumerable<Order>> GetAllAsync();
    Task AddAsync(Order order);
    Task UpdateAsync(Order order);
    Task DeleteAsync(Guid id);
}
Enter fullscreen mode Exit fullscreen mode

IOrderItemRepository
Path: ECommerce.Domain/Repositories/IOrderItemRepository.cs

using ECommerce.Domain.Entities;

namespace ECommerce.Domain.Repositories;

public interface IOrderItemRepository
{
    Task<OrderItem?> GetByIdAsync(Guid id);
    Task<IEnumerable<OrderItem>> GetAllAsync();
    Task AddAsync(OrderItem orderItem);
    Task UpdateAsync(OrderItem orderItem);
    Task DeleteAsync(Guid id);
    Task<IEnumerable<OrderItem>> GetByOrderIdAsync(Guid orderId);
}
Enter fullscreen mode Exit fullscreen mode

Infrastructure Layer - Concrete Repository Implementations

Base Packages (Required for All Databases)
Install these in your Infrastructure project (ECommerce.Infrastructure):

dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore.Abstractions --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore.Relational --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore.Tools --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore.Design --version 8.0.21
Enter fullscreen mode Exit fullscreen mode

These are the core EF packages that provide DbContext, DbSet, and model-building APIs.

AppDbContext

using ECommerce.Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
        }

        // DbSets
        public DbSet<Product> Products { get; set; } = null!;
        public DbSet<Customer> Customers { get; set; } = null!;
        public DbSet<Order> Orders { get; set; } = null!;
        public DbSet<OrderItem> OrderItems { get; set; } = null!;

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Product
            modelBuilder.Entity<Product>(b =>
            {
                b.ToTable("Products");
                b.HasKey(p => p.Id);
                b.Property(p => p.Name).IsRequired().HasMaxLength(200);
                b.Property(p => p.Price).HasColumnType("decimal(18,2)");
            });

            // Customer
            modelBuilder.Entity<Customer>(b =>
            {
                b.ToTable("Customers");
                b.HasKey(c => c.Id);
                b.Property(c => c.FirstName).IsRequired().HasMaxLength(200);
                b.Property(c => c.Email).IsRequired().HasMaxLength(256);
                b.HasMany(c => c.Orders)
                 .WithOne(o => o.Customer)
                 .HasForeignKey(o => o.CustomerId)
                 .OnDelete(DeleteBehavior.Cascade);
            });

            // Order
            modelBuilder.Entity<Order>(b =>
            {
                b.ToTable("Orders");
                b.HasKey(o => o.Id);
                b.Property(o => o.OrderDate).IsRequired();
                b.Property(o => o.TotalAmount).HasColumnType("decimal(18,2)");
                b.HasMany(o => o.Items)
                 .WithOne(oi => oi.Order)
                 .HasForeignKey(oi => oi.OrderId)
                 .OnDelete(DeleteBehavior.Cascade);
            });

            // OrderItem
            modelBuilder.Entity<OrderItem>(b =>
            {
                b.ToTable("OrderItems");
                b.HasKey(oi => oi.Id);
                b.Property(oi => oi.ProductName).IsRequired().HasMaxLength(200);
                b.Property(oi => oi.UnitPrice).HasColumnType("decimal(18,2)");
                b.Property(oi => oi.Quantity).IsRequired();
            });

            // If you want to seed or add indexes later, do it here.
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ProductRepository
Path: ECommerce.Infrastructure/Repositories/ProductRepository.cs

using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;
using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Repositories;

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

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

    public async Task AddAsync(Product product)
    {
        await _context.Products.AddAsync(product);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteAsync(Guid id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product != null)
        {
            _context.Products.Remove(product);
            await _context.SaveChangesAsync();
        }
    }

    public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync();

    public async Task<Product?> GetByIdAsync(Guid id) => await _context.Products.FindAsync(id);

    public async Task UpdateAsync(Product product)
    {
        _context.Products.Update(product);
        await _context.SaveChangesAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

CustomerRepository
Path: ECommerce.Infrastructure/Repositories/CustomerRepository.cs

using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Repositories;

public class CustomerRepository : ICustomerRepository
{
    private readonly AppDbContext _context;

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

    public async Task AddAsync(Customer customer)
    {
        await _context.Customers.AddAsync(customer);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteAsync(Guid id)
    {
        var customer = await _context.Customers.FindAsync(id);
        if (customer != null)
        {
            _context.Customers.Remove(customer);
            await _context.SaveChangesAsync();
        }
    }

    public async Task<IEnumerable<Customer>> GetAllAsync() => await _context.Customers.ToListAsync();

    public async Task<Customer?> GetByIdAsync(Guid id) => await _context.Customers.FindAsync(id);

    public async Task UpdateAsync(Customer customer)
    {
        _context.Customers.Update(customer);
        await _context.SaveChangesAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

OrderRepository
Path: ECommerce.Infrastructure/Repositories/OrderRepository.cs

using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;
using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Repositories;

public class OrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;

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

    public async Task AddAsync(Order order)
    {
        await _context.Orders.AddAsync(order);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteAsync(Guid id)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order != null)
        {
            _context.Orders.Remove(order);
            await _context.SaveChangesAsync();
        }
    }

    public async Task<IEnumerable<Order>> GetAllAsync() =>
        await _context.Orders.Include(o => o.Items).ToListAsync();

    public async Task<Order?> GetByIdAsync(Guid id) =>
        await _context.Orders.Include(o => o.Items).FirstOrDefaultAsync(o => o.Id == id);

    public async Task UpdateAsync(Order order)
    {
        _context.Orders.Update(order);
        await _context.SaveChangesAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

OrderItemRepository
Path: ECommerce.Infrastructure/Repositories/OrderItemRepository.cs

using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Repositories;

public class OrderItemRepository : IOrderItemRepository
{
    private readonly AppDbContext _context;

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

    public async Task AddAsync(OrderItem orderItem)
    {
        await _context.OrderItems.AddAsync(orderItem);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteAsync(Guid id)
    {
        var item = await _context.OrderItems.FindAsync(id);
        if (item != null)
        {
            _context.OrderItems.Remove(item);
            await _context.SaveChangesAsync();
        }
    }

    public async Task<IEnumerable<OrderItem>> GetAllAsync() => await _context.OrderItems.ToListAsync();

    public async Task<OrderItem?> GetByIdAsync(Guid id) => await _context.OrderItems.FindAsync(id);

    public async Task UpdateAsync(OrderItem orderItem)
    {
        _context.OrderItems.Update(orderItem);
        await _context.SaveChangesAsync();
    }
  public async Task<IEnumerable<OrderItem>> GetByOrderIdAsync(Guid orderId)
 {
     return await _context.OrderItems
         .Where(oi => oi.OrderId == orderId)
         .ToListAsync();
 }
}
Enter fullscreen mode Exit fullscreen mode

Updated Program.cs

using ECommerce.Domain.Repositories;
using ECommerce.Infrastructure.Repositories;

var builder = WebApplication.CreateBuilder(args);

// ------------------------------------------------------
// Add Controllers
// ------------------------------------------------------
builder.Services.AddControllers();


// ------------------------------------------------------
//  Repository Registrations
// ------------------------------------------------------
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<ICustomerRepository, CustomerRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderItemRepository, OrderItemRepository>();



// ------------------------------------------------------
//  Swagger
// ------------------------------------------------------
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


var app = builder.Build();

// ------------------------------------------------------
// Middleware Pipeline
// ------------------------------------------------------
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
app.MapControllers();

app.Run();

Enter fullscreen mode Exit fullscreen mode

Next Lecture Preview
Lecture 2D : Implementing Application Services (Service Layer and DTOs)

After defining entities and repositories, the next layer in Clean Architecture is the Application Layer (or Service Layer)

Top comments (0)