Module 5 of 6
Practical Example: E-commerce Platform
Let's take an example of an order management application in an e-commerce platform. Here is how the Repository and Unit of Work patterns can be used together:
Scenario: Creating an Order
- An order contains information about the customer, the items ordered, and the total amount.
- The operation must:
- Create a new order.
- Update the inventory for the items involved.
- Record the payment transaction.
Implementation Code
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
// Inject the UnitOfWork to coordinate transactions
_unitOfWork = unitOfWork;
}
public async Task<bool> CreateOrderAsync(Order order, IEnumerable<OrderItem> orderItems)
{
try
{
// Step 1: Add the order
await _unitOfWork.Repository<Order>().AddAsync(order);
// Step 2: Update inventory for each ordered item
foreach (var item in orderItems)
{
var product = await _unitOfWork.Repository<Product>().GetByIdAsync(item.ProductId);
if (product == null || product.Stock < item.Quantity)
{
throw new InvalidOperationException("Insufficient stock.");
}
product.Stock -= item.Quantity; // Decrease stock
_unitOfWork.Repository<Product>().Update(product);
}
// Step 3: Record the payment transaction
var payment = new Payment
{
OrderId = order.Id,
Amount = orderItems.Sum(i => i.Price * i.Quantity),
PaymentDate = DateTime.UtcNow
};
await _unitOfWork.Repository<Payment>().AddAsync(payment);
// Step 4: Save all changes in a single transaction
await _unitOfWork.SaveChangesAsync();
return true; // Returns true if everything succeeded
}
catch (Exception)
{
// Error handling: the transaction will be rolled back on exception
throw;
}
}
}
Explanations
- Logic Isolation: Each repository handles a specific entity (Order, Product, Payment).
- Single Transaction: All operations are grouped via the Unit of Work, ensuring data consistency.
- Ease of Maintenance: Application logic remains clear and well-structured.
Check your understanding
In the order-creation scenario, what happens if stock is insufficient?
Why call SaveChangesAsync() only once at the end of the method?