← Назад к вопросам

Как работает паттерн Unit of Work? Зачем он нужен совместно с Repository?

1.7 Middle🔥 231 комментариев
#Архитектура и микросервисы#ООП и паттерны проектирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает паттерн Unit of Work

Unit of Work (UoW) — это паттерн, который координирует работу с несколькими репозиториями или объектами данных, гарантируя, что все изменения в рамках одной бизнес-транзакции либо полностью сохраняются, либо полностью откатываются. Он служит контейнером для набора операций, которые должны быть выполнены как единое целое.

Основная логика работы:

  1. Сбор изменений: UoW отслеживает все объекты (например, сущности), которые были изменены (добавлены, изменены, удалены) в ходе бизнес-операции.
  2. Координация сохранения: Когда операция завершается (например, после выполнения всех действий в сервисе), UoW выполняет фиксацию (Commit) всех накопленных изменений в базу данных одним транзакционным действием.
  3. Обработка ошибок: Если при сохранении происходит ошибка, UoW обеспечивает откат (Rollback) всех изменений, поддерживая целостность данных.

Пример реализации в C# (псевдокод):

public interface IUnitOfWork
{
    IRepository<Product> Products { get; }
    IRepository<Order> Orders { get; }
    void Commit(); // Сохраняет все изменения
    void Rollback(); // Откатывает изменения
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private Dictionary<Type, object> _repositories;

    public UnitOfWork(DbContext context)
    {
        _context = context;
        _repositories = new Dictionary<Type, object>();
    }

    public IRepository<T> GetRepository<T>() where T : class
    {
        if (!_repositories.ContainsKey(typeof(T)))
        {
            _repositories[typeof(T)] = new Repository<T>(_context);
        }
        return (IRepository<T>) _repositories[typeof(T)];
    }

    public void Commit()
    {
        _context.SaveChanges(); // EF Core выполняет транзакцию
    }

    public void Rollback()
    {
        // Откат через изменение состояния объектов или повторное открытие контекста
        foreach (var entry in _context.ChangeTracker.Entries())
        {
            entry.State = EntityState.Detached;
        }
    }
}

Зачем Unit of Work нужен совместно с Repository

Сочетание Repository и Unit of Work создает мощный и гибкий подход к работе с данными. Вот ключевые причины их совместного использования:

1. Управление транзакционностью на уровне бизнес-операций

  • Repository отвечает только за CRUD-операции для конкретной сущности (например, ProductRepository работает только с продуктами).
  • Unit of Work объединяет несколько репозиториев и обеспечивает транзакционность для сложных операций, затрагивающих разные сущности. Например, создание заказа (Order) может потребовать обновления списка продуктов (Product) и создания записи оплаты (Payment). UoW гарантирует, что все эти изменения будут сохранены или откачены вместе.
public class OrderService
{
    private readonly IUnitOfWork _unitOfWork;

    public OrderService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void CreateOrder(Order order, List<Product> updatedProducts)
    {
        // Используем репозитории через UoW
        _unitOfWork.GetRepository<Order>().Add(order);
        foreach (var product in updatedProducts)
        {
            _unitOfWork.GetRepository<Product>().Update(product);
        }

        // ВСЕ изменения фиксируются одним Commit
        _unitOfWork.Commit();
    }
}

2. Сокращение дублирования и улучшение тестируемости

  • Repository абстрагирует доступ к данным, что позволяет легко заменять источник данных (например, для тестов).
  • Unit of Work абстрагирует транзакционную логику, исключая необходимость вручную управлять транзакциями (DbContext.SaveChanges()) в каждом сервисе. Это делает код бизнес-логики чище и позволяет тестировать операции без реальной базы данных (через моки UoW).

3. Эффективное управление контекстом данных (особенно в ORM, таких как Entity Framework)

  • В EF Core DbContext сам является реализацией Unit of Work и Repository (так как отслеживает изменения и предоставляет DbSet для сущностей). Однако, явное выделение UoW и Repository позволяет:
    - **Создать более строгие абстракции**, не зависящие от конкретной ORM.
    - **Контролировать время жизни контекста** (например, одна транзакция на HTTP-запрос в веб-приложениях).
    - **Изолировать бизнес-логику от инфраструктурных деталей** ORM.

4. Обеспечение согласованности данных

  • При одновременном изменении нескольких сущностей Repository по отдельности не может гарантировать, что все их обновления пройдут успешно. Unit of Work решает эту проблему, предоставляя единый механизм фиксации, который либо сохраняет все изменения, либо ни одного, предотвращая частичные обновления и расхождения данных.

Практический пример сочетания:

// Интерфейс репозитория (Repository Pattern)
public interface IRepository<T>
{
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
    T GetById(int id);
    IEnumerable<T> GetAll();
}

// Сервис использует UoW для координации репозиториев
public class InventoryService
{
    private readonly IUnitOfWork _unitOfWork;

    public InventoryService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void TransferStock(int productId, int fromWarehouseId, int toWarehouseId, int quantity)
    {
        var productRepo = _unitOfWork.GetRepository<Product>();
        var warehouseRepo = _unitOfWork.GetRepository<Warehouse>();

        var product = productRepo.GetById(productId);
        var fromWarehouse = warehouseRepo.GetById(fromWarehouseId);
        var toWarehouse = warehouseRepo.GetById(toWarehouseId);

        // Бизнес-логика: обновление нескольких сущностей
        fromWarehouse.RemoveStock(product, quantity);
        toWarehouse.AddStock(product, quantity);

        warehouseRepo.Update(fromWarehouse);
        warehouseRepo.Update(toWarehouse);

        // Транзакционное сохранение через UoW
        try
        {
            _unitOfWork.Commit();
        }
        catch (Exception)
        {
            _unitOfWork.Rollback();
            throw;
        }
    }
}

Вывод

Unit of Work и Repository работают в tandem: Repository предоставляет абстракцию для доступа к данным конкретной сущности, а Unit of Work предоставляет абстракцию для транзакционного управления изменениями множества сущностей. Их совместное использование позволяет строить чистую, тестируемую и надежную архитектуру данных в сложных бизнес-приложениях, где операции часто затрагивают несколько объектов и требуют гарантий целостности.