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 any Clean Architecture or Domain-Driven Design (DDD) project, the Domain Layer is the heart of the system.
 It contains the core business entities, rules, and relationships that define what your system does - independent of how it's implemented (no database, UI, or framework dependencies).
In this lecture, we'll implement the core entities of our E-Commerce System:
- Customer - who makes purchases
- Product - what is being sold
- Order - represents a customer's purchase
- OrderItem - the individual items within an order
By the end, we'll have a pure, framework-agnostic model layer ready to connect with our Application and Infrastructure layers later on.
Clean Architecture Recap
Before we dive into the code, recall the four layers in our solution:
This lecture focuses entirely on ECommerce.Domain.
Domain Folder Structure
Create this structure inside the ECommerce.Domain project:
Entities Overview
We'll define four entities:
- Customer Represents a buyer or user placing orders
- Product Represents an item available for sale
- Order Represents a transaction made by a customer
- OrderItem Represents a line item within an order
1. Customer.cs
Path: ECommerce.Domain/Entities/Customer.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ECommerce.Domain.Entities;
public class Customer
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required(ErrorMessage = "First name is required.")]
[MaxLength(50, ErrorMessage = "First name cannot exceed 50 characters.")]
public string FirstName { get; set; } = string.Empty;
[Required(ErrorMessage = "Last name is required.")]
[MaxLength(50, ErrorMessage = "Last name cannot exceed 50 characters.")]
public string LastName { get; set; } = string.Empty;
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Invalid email address.")]
[MaxLength(100, ErrorMessage = "Email cannot exceed 100 characters.")]
public string Email { get; set; } = string.Empty;
[Phone(ErrorMessage = "Invalid phone number.")]
[MaxLength(20, ErrorMessage = "Phone number cannot exceed 20 characters.")]
public string PhoneNumber { get; set; } = string.Empty;
// Navigation Property
public ICollection<Order> Orders { get; set; } = new List<Order>();
// Computed Property (Not mapped to DB)
[NotMapped]
public string FullName => $"{FirstName} {LastName}";
}
2. Product.cs
Path: ECommerce.Domain/Entities/Product.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ECommerce.Domain.Entities;
public class Product
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required(ErrorMessage = "Product name is required.")]
[MaxLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")]
public string Name { get; set; } = string.Empty;
[MaxLength(500, ErrorMessage = "Description cannot exceed 500 characters.")]
public string Description { get; set; } = string.Empty;
[Range(0, double.MaxValue, ErrorMessage = "Price must be a non-negative value.")]
[Column(TypeName = "decimal(18,2)")] // Ensures proper precision in database
public decimal Price { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "Stock cannot be negative.")]
public int Stock { get; set; }
// Domain Logic
public void ReduceStock(int quantity)
{
if (quantity > Stock)
throw new InvalidOperationException("Insufficient stock.");
Stock -= quantity;
}
}
3. OrderItem.cs
Path: ECommerce.Domain/Entities/OrderItem.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ECommerce.Domain.Entities;
public class OrderItem
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public Guid OrderId { get; set; }
[Required]
public Guid ProductId { get; set; }
[Required(ErrorMessage = "Product name is required.")]
[MaxLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")]
public string ProductName { get; set; } = string.Empty;
[Range(0, double.MaxValue, ErrorMessage = "Unit price must be non-negative.")]
[Column(TypeName = "decimal(18,2)")]
public decimal UnitPrice { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Quantity must be at least 1.")]
public int Quantity { get; set; }
// Navigation Property
public Order? Order { get; set; }
// Computed Property (Not mapped to DB)
[NotMapped]
public decimal TotalPrice => UnitPrice * Quantity;
}
4. Order Status Enum
Path: ECommerce.Domain/Enums/OrderStatus.cs
namespace ECommerce.Domain.Enums
{
public enum OrderStatus
{
Pending,
Completed,
Cancelled
}
}
4. Order.cs
Path: ECommerce.Domain/Entities/Order.cs
using ECommerce.Domain.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ECommerce.Domain.Entities
{
public class Order
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public Guid CustomerId { get; set; }
[Required]
public DateTime OrderDate { get; set; } = DateTime.UtcNow;
[Range(0, double.MaxValue, ErrorMessage = "Total amount cannot be negative.")]
[Column(TypeName = "decimal(18,2)")]
public decimal TotalAmount { get; private set; }
[Required]
public OrderStatus Status { get; set; } = OrderStatus.Pending;
// Navigation Properties
public Customer? Customer { get; set; }
public ICollection<OrderItem> Items { get; set; } = new List<OrderItem>();
// Business Logic
public void CalculateTotal()
{
TotalAmount = Items.Sum(i => i.TotalPrice);
}
public void MarkAsCompleted() => Status = OrderStatus.Completed;
public void MarkAsCancelled() => Status = OrderStatus.Cancelled;
}
}
Relationships Summary
Next Lecture Preview
Lecture 2C: Implementing Repository Interfaces (Domain Layer)
We'll define clean contracts for data access:
- IProductRepository
- ICustomerRepository
- IOrderRepository
- IOrderItemRepository
These interfaces will later be implemented in the Infrastructure layer using EF Core.
Top comments (0)