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);
}
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);
}
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);
}
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);
}
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
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.
}
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
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)