Repository and Unit of Work Pattern - Practical Example: E-commerce Platform

Case Study: Discover the joint usage of the two patterns to manage orders, inventory, and payments in an e-commerce platform.

Summary

This article is part of a series covering the Repository and Unit of Work patterns:

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

  1. An order contains information about the customer, the items ordered, and the total amount.
  2. 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)
    {
        // Injection de l'UnitOfWork pour coordonner les transactions
        _unitOfWork = unitOfWork;
    }

    public async Task<bool> CreateOrderAsync(Order order, IEnumerable<OrderItem> orderItems)
    {
        try
        {
            // Étape 1 : Ajouter la commande
            await _unitOfWork.Repository<Order>().AddAsync(order);

            // Étape 2 : Mettre à jour l'inventaire pour chaque article commandé
            foreach (var item in orderItems)
            {
                var product = await _unitOfWork.Repository<Product>().GetByIdAsync(item.ProductId);
                if (product == null || product.Stock < item.Quantity)
                {
                    throw new InvalidOperationException("Stock insuffisant.");
                }

                product.Stock -= item.Quantity; // Réduction du stock
                _unitOfWork.Repository<Product>().Update(product);
            }

            // Étape 3 : Enregistrer la transaction de paiement
            var payment = new Payment
            {
                OrderId = order.Id,
                Amount = orderItems.Sum(i => i.Price * i.Quantity),
                PaymentDate = DateTime.UtcNow
            };
            await _unitOfWork.Repository<Payment>().AddAsync(payment);

            // Étape 4 : Sauvegarder toutes les modifications dans une transaction unique
            await _unitOfWork.SaveChangesAsync();

            return true; // Retourne vrai si tout s'est bien passé
        }
        catch (Exception)
        {
            // Gestion des erreurs : la transaction sera annulée en cas d'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.

Next: Conclusion and Lessons Learned